در توسینسو تدریس کنید

و

با دانش خود درآمد کسب کنید

آموزش برنامه نویسی C قسمت 23 : Thread

سلام و وقت بخیر. بنا بر تعریف، thread به کوچکترین واحد پردازشی گفته میشود که طبق یک زمان بندی روی آن کار میشود. یک process میتواند شامل چندین thread باشد که بصورت غیر همزمان اجرا میشوند. این اجرای غیر همزمان باعث میشود که هر thread بخشی از یک کار مستقل را به عهده بگیرد و انجام دهد. بنابراین چندین thread که داخل یک process انجام میشوند، وظیفه ای که به process واگدار شده را انجام میدهند. مجموع این thread ها در حقیقت ظرفیت عملیاتی یک process است. حال چرا باید از thread استفاده کنیم و اصلا به چه دردی میخورد ؟ فرض کنید یک process داریم که که مجموعه ای از ورودی ها را بصورت real time میگیرد و متناظر با هر ورودی، یک خروجی خاص تولید میکند. حال اگر این process شامل thread نباشد، تمام پردازش بصورت همزمان انجام میشود، یعنی یک ورودی میگیرد و یک خروجی میدهد. مشکل این روش این است که process نمیتواند تا قبل از اینکه کار ورودی قبلی را انجام نداده، ورودی جدیدی بگیرد. حال اگر یک ورودی سنگین تر هم باشد و زمان بیشتری بگیرد، ورودی های بعدی همه باید منتظر بمانند تا کار آن ورودی سنگین انجام شود.

ابتدا باید درک کنیم که تفاوت بین thread و process چیست ؟!


1- process ها فضای آدرس خود را ( Address Space ) به اشتراک نمیگذارند، در حالی که thread های زیر مجموعه یک process این کار را (تقسیم فضای آدرس) را انجام میدهند.

2- بر اساس نکته بالا، اجرای process ها مستقل از یکدیگر صورت میگیرد و وظیفه غیرهمزمان انجام شدن آنها توسط kernel مدیریت میشود. در حالی که مدیریت غیر همزمان انجام شدن thread های یک process توسط خود process انجام میشود.

3- در thread ها context switching سریعتر از process ها انجام میشود.

4- ارتباطات میان process ها فقط از طریق راه های استاندارد ارتباطی process ها در سیستم عامل انجا میشود در حالی که thread های یک process خیلی راحت تر بسیاری از منابع خود مثل حافظه و موارد دیگر را میان هم به اشتراک میگذارند.

Thread ها میتوانند هم در kernel space وجود داشته باشند و هم در user space


در user space بوجود آوردن و از بین بردن thread ها توسط کتابخانه های thread انجام میشود. این thread ها برای kernel ناشناخته هستند و kernel هیچ نقشی در مدیریت آنها ندارد. در user space خود thread تصمیم میگیرد که چه زمانی حافظه پردازنده را آزاد کند. نکته مثبت thread در user space این است که switch کردن میان آنها با حداقل overhead انجام میشود و بسیار سریع است. نکته منفی آن هم این است که در این لایه اصطلاحا میان thread ها رابطه co-operative multitasking وجود دارد. یعنی اگر یکی از thread ها block شود، کل process از بین میرود و block میشود. در kernel space خود kernel وظیفه بوجود آوردن و از بین بردن thread ها را به عهده دارد. در این حالت به ازای هر thread در user space بک thread متناظر هم در kernel space وجود دارد. چون این thread ها توسط kernel مدیریت میشوند، برخلاف thread های user space که با co-operative multitasking کار میکنند، thread های kernel space از preemptive multitasking استفاده میکنند. در این حالت میتوان یک thread را با حق اولویت پردازش بالاتری اجرا کرد. همچنین اگر یکی از thread ها block شود، مشکلی برای process بوجود نمی آید. اما switching میان thread ها در این لایه کمی کند تر نسبت به user space است.

در اکثر سناریو ها از مدل kernel space بدلیل عملکرد بهتر استفاده میشود


همانطور که process ها با pid مشخص میشوند، thread ها هم با thread id مشخص میشوند. به نکات زیر در این مورد دقت کنید.

1- یک pid در کل فضای سیستم عامل منحصر به فرد است. در حالی که یک thread id فقط در زیر مجموعه process خودش منحصر به فرد است.

2- یک pid همیشه یک عدد int است. در حالی که thread id میتواند حتی struct هم باشد.

3- یک pid را خیلی راحت میشود چاپ کرد ولی برای thread id همیشه اینطور نیست.

برای thread id یک type به نام pthread_t وجود دارد که توجه داشته باشید structure است. پس یک تابع باید وجود داشته باشد که thread id ها را بتواند مقایسه کند.

#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2);

تابع بالا 2 تا thread id میگیرد و اگر برابر باشند مقدار غیر صفر و اگر نابرابر باشند صفر برمیگرداند.اگر در شرایطی یک thread بخواهد thread id خود را بداند از تابع زیر استفاده میکنیم.

#include <pthread.h>
pthread_t pthread_self(void);

پس از تابع pthread__self برای شناساندن thread id به خود thread استفاده میکنیم.در حالت عادی وقتی یک برنامه اجرا میشود و تبدیل به یک process میشود، با یک thread پیش فرض شروع میشود. در واقع هر process حداقل یک thread را دارد. هر process میتواند با دستور زیر برای خودش thead های بیشتری تولید کند.

#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void), void *restrict arg)

این تابع نیاز به 4 آرگومان دارد.

1- آرگومان اول آدرس pthread__t type است. وقتی تابع با موفقیت صدا زده شد، آرگومان اول مقدار thread id را نگه میدارد.

2- آرگومان دوم صفت ها ( attributes ) هایی است که میخواهیم thread داشته باشد. مثل اولویت.

3- آرگومان سوم یک function pointer است. فقط به یاد داشته باشید که هر thread با یک تابع شروع میشود و آدرس آن تابع اینجا پاس داده میشود تا kernel بداند کدام تابع کدام thread را ایجاد کرده.

4- شاید تابعی که thread را تولید میکند آرگومان های بیشتری داشته باشد، این آرگومان ها را بصورت اشاره گر به void type در آرگومان چهارم پاس میدهیم. حالا چرا void type ؟؟ چون اگر یک تابع تعداد بیشتری آرگومان داشت، این شاره گر میتواند به یک structure که این آرگومان ها را نگه میدارد شاره کنذ.

حال با توجه به چیزهایی که تا حالا خوندیم، مثال زیر را ببینید:

#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>

pthread_t tid[2];

void* doSomeThing(void *arg)
{
    unsigned long i = 0;
    pthread_t id = pthread_self();

    if(pthread_equal(id,tid[0]))
    {
        printf("\n First thread processing\n");
    }
    else
    {
        printf("\n Second thread processing\n");
    }

    for(i=0; i<(0xFFFFFFFF);i++);

    return NULL;
}

int main(void)
{
    int i = 0;
    int err;

    while(i < 2)
    {
        err = pthread_create(&(tid[i]), NULL, &doSomeThing, NULL);
        if (err != 0)
            printf("\ncan't create thread :[%s]", strerror(err));
        else
            printf("\n Thread created successfully\n");

        i++;
    }

    sleep(5);
    return 0;
}

برای کامپایل کمی دستور gcc را باید تغییر دهید:

gcc -pthread thread.c -o thread

دستورات رو تو فایل thread.c نوشتم و برای اجرا :

[root@CentOS6 c]# ./thread 

 Thread created successfully

 Thread created successfully

 Second thread processing

 First thread processing

[root@CentOS6 c]#

حال این کد چه کار میکند ؟

1- از pthread_create() function استفاده میکند تا 2 تا thread درست کند.

2- تابع شروع کننده برای هر دو thread یکی است.

3- داخل تایع doSomeThing از pthread-self و pthread-equal استفاده میشود تا مشخص شود کدام thread اول و کدام دوم تولید شده اند.

توجه داشته باشید که ترتیب اجرا کردن thread ها ثابت نیست و بستگی به الگوریتم های زمان بندی سیستم عامل دارد. یک بار دیگر به کد دقت کنید.... شاید بپرسید اون sleep چیه دیگه ؟؟ پاکش کنید و دوباره کد رو اجرا کنید... خروجی میشه مث زیر:

[root@CentOS6 c]# ./thread 

 Thread created successfully

 Thread created successfully
[root@CentOS6 c]# 

شاید برای شما یک خط processing هم چاپ کند، برای من یکی هم چاپ نکرد !!! حالا چرا این اتفاق افتاد؟؟ دلیل آن این است که قبل از اینکه دومین thread یا حتی اولین thread هم بخواهد کاری انجام دهد، thread پدر کارش تمام شده و به پایان رسیده است. یادتونه گفتم هر برنامه بصورت پیش فرض حداقل یه دونه thread داره؟؟ تابع main هم یه thread پیش فرض داره و پدر این thread هاییه که ما درست کردیم. پس قبل از اینکه thread های فرزند کاری انجام بدن، کار تابع main تموم میشه و برنامه بسته میشه !!! پس حداکثر عمر یک thread فرزند در برنامه وابسته به عمر thread پیش فرض ( پدر ) تابع main است. حال اگر بخواهیم این thread پدر را مجبور کنیم تا موقعی که کار thread های فرزند تمام نشده صبر کند، از تابع زیر استفاده میکنیم.

#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);

این تابع مطمئن میشود که تا موقعی که کار thread فرزند تمام نشده، thread پدرش terminate نشود. این تابع در thread پدر تعریف میشود. آرگومان اول thread id اون thread ای است که منتظرش میماند تا تمام شود و آرگومان دوم برابر مقداری است که thread ای که منتظرش بوده برمیگرداند. اگر میخوایم مقداری برنگردد آنرا null میگذاریم. بطور کلی یک thread به 3 روش میتواند terminate شود ( ینی خاتمه پیدا کند ) :

1- از جایی که صدا زده شده خاتمه یاید.

2- یک thread دیگر با تابع pthread__cancel آنرا خاتمه دهد.

3- خودش خودش را خاتمه دهد که این کار با تابع pthread__exit انجام میشود.

#include <pthread.h>
void pthread_exit(void *rval_ptr);

این تابع فقط یک آرگومان میگیرد و این مقدار را به پدر خود پاس میدهد. در واقع مقدار این آرگومان، برابر با چیزی است که یک thread فرزند در نهایت کار خود تولید میکند و آنرا به پدر خود پاس میدهد و thread پدر این مقدار برگشتی را در آرگومان دوم تابع pthread__join دریافت میکند. ( یکم پیچیده شد، ببخشید !!! ). حال اگر بخواهیم مثال بالا را بهبود بدهبم و از توابع جدیدی که یاد گرفتیم استفاده کنیم، کدمان بصورت زیر تغییر پیدا میکند.

#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>

pthread_t tid[2];
int ret1,ret2;

void* doSomeThing(void *arg)
{
    unsigned long i = 0;
    pthread_t id = pthread_self();

    for(i=0; i<(0xFFFFFFFF);i++);

    if(pthread_equal(id,tid[0]))
    {
        printf("\n First thread processing done\n");
        ret1  = 100;
        pthread_exit(&ret1);
    }
    else
    {
        printf("\n Second thread processing done\n");
        ret2  = 200;
        pthread_exit(&ret2);
    }

    return NULL;
}

int main(void)
{
    int i = 0;  
    int err;
    int *ptr[2];

    while(i < 2)
    {
        err = pthread_create(&(tid[i]), NULL, &doSomeThing, NULL);
        if (err != 0)
            printf("\ncan't create thread :[%s]", strerror(err));
        else
            printf("\n Thread created successfully\n");

        i++;
    }

    pthread_join(tid[0], (void**)&(ptr[0]));
    pthread_join(tid[1], (void**)&(ptr[1]));

    printf("\n return value from first thread is [%d]\n", *ptr[0]);
    printf("\n return value from second thread is [%d]\n", *ptr[1]);

    return 0;
}

برای من خروجی رو اینجوری نشون میده :

[root@CentOS6 c]# ./thread

 Thread created successfully

 Thread created successfully

 Second thread processing done

 First thread processing done

 return value from first thread is [100]

 return value from second thread is [200]
[root@CentOS6 c]# 
[root@CentOS6 c]# 

حالا اگر دوباره بخوایم کد رو مرور کنیم:

1- با استفاده از pthread_create دو تا thread درست کردیم.

2- thread هایی که درست کردیم با تابع pthread_exit از بین میروند و مقداری را برمیگردانند.

3- در تابع main پس از ایجاد thread ها با تابع pthread_join منتظر thread ها میمانیم تا کارشان تمام شود.

4- وقتی کار thread ها تمام شد خروجی خود را با استفاده از آرگومان دوم تابع pthread_join به پدر خود ارسال میکنند. (آرگومان دوم pthread-join مقدار pthread-exit را قبول میکند.)

ببخشید این مطلب طولانی و پیچیده شد... میخواستم یک قسمت بشه thread .

پایان قسمت بیست و سوم

نویسنده : سید محمد باقر موسوی

منبع : جزیره برنامه نویسی وب سایت توسینسو

هرگونه نشر و کپی برداری بدون ذکر منبع و نام نویسنده دارای اشکال اخلاقی است

#thread_چیست #برنامه_نویسی_به_زبان_c #برنامه_نویسی_c_در_لینوکس #نوشتن_shell_script_در_لینوکس #زبان_برنامه_نویسی_c_در_لینوکس #برنامه_نویسی_c_در_محیط_لینوکس #برنامه_نویسی_به_زبان_c_در_لینوکس #آموزش_گام_به_گام_برنامه_نویسی_c #برنامه_نویسی_c_در_linux #آموزش_برنامه_نویسی_c_در_لینوکس
عنوان
1 آموزش برنامه نویسی C قسمت 1 : نصب محیط برنامه نویسی رایگان
2 آموزش برنامه نویسی C قسمت 2 : Hello World رایگان
3 آموزش برنامه نویسی C قسمت 3 : Data Types رایگان
4 آموزش برنامه نویسی C قسمت 4 : Data Types رایگان
5 آموزش برنامه نویسی C قسمت 5 : اشاره گر ها رایگان
6 آموزش برنامه نویسی C قسمت 6 : آرایه ها رایگان
7 آموزش برنامه نویسی C قسمت 7 : ساختار شرط IF رایگان
8 آموزش برنامه نویسی C قسمت 8 : حلقه for رایگان
9 آموزش برنامه نویسی C قسمت 9 : حلقه While رایگان
10 آموزش برنامه نویسی C قسمت 10 : Struct رایگان
11 آموزش برنامه نویسی C قسمت 11 : تابع دریافت ورودی scanf رایگان
12 آموزش برنامه نویسی C قسمت 12 : فایل های متنی و باینری رایگان
13 آموزش برنامه نویسی C قسمت 13 : توابع رایگان
14 آموزش برنامه نویسی C قسمت 14 : توابع اشاره گر رایگان
15 آموزش برنامه نویسی C قسمت 15 : argc argv رایگان
16 آموزش برنامه نویسی C قسمت 16 : Multiple Source Files رایگان
17 آموزش برنامه نویسی C قسمت 17 : String Functions & Operations رایگان
18 آموزش برنامه نویسی C قسمت 18 : Char Pointers VS Array Char رایگان
19 آموزش برنامه نویسی C قسمت 19 : Binary & Unary Operations رایگان
20 آموزش برنامه نویسی C قسمت 20 : Type Casting رایگان
21 آموزش برنامه نویسی C قسمت 21 : readdir & opendir functions رایگان
22 آموزش برنامه نویسی C قسمت 22 : Fork Function رایگان
23 آموزش برنامه نویسی C قسمت 23 : Thread رایگان
24 آموزش برنامه نویسی C قسمت 24 : Switch Case Statement رایگان
25 آموزش برنامه نویسی C قسمت 25 : qsort رایگان
26 آموزش برنامه نویسی C قسمت 26 : Socket Programming رایگان
27 آموزش برنامه نویسی C قسمت 27 : لیست پیوندی (Linked List) رایگان
زمان و قیمت کل 0″ 0
0 نظر

هیچ نظری ارسال نشده است! اولین نظر برای این مطلب را شما ارسال کنید...

نظر شما
برای ارسال نظر باید وارد شوید.
از سرتاسر توسینسو
تنظیمات حریم خصوصی
تائید صرفنظر
×

تو می تونی بهترین نتیجه رو تضمینی با بهترین های ایران بدست بیاری ، پس مقایسه کن و بعد خرید کن : فقط توی جشنواره تابستانه می تونی امروز ارزونتر از فردا خرید کنی ....