الدرس الثاني : ‫حماية المنطقة الحرجة‬

سلسلة من الدروس في الـ POSIX

الدرس الثاني : حماية المنطقة الحرجة
 

Pthread_Attributes:

لضبط مميزات الـ Thread يجب أولاً إنشاء متحول من نوع pthread_attr_t ثم ضبط الميزات باستخدام مجموعة من التوابع. مجموعة المميزات التي يمكن التحكم بها:

  • Scheduling scope :تحدد الـ Thread فيما إذا كان خاص بالنظام PTHREAD_SCOPE_SYSTEM و هي القيمة الإفتراضية، أو بالمستخدم PTHREAD_SCOPE_PROCESS. يتم ذلك من خلال التابع

pthread_attr_{set|get}scope();

  • Detach state : تحدد إذا كان الـ Thread يمكن انتظاره من قبل Thread آخر PTHREAD_CREATE_JOINABLE و بالتالي الحصول على معلومات عن الـ Threadو هي القيمة الافتراضية في Pthread، أو إذا كان منفصل عن أي Thread آخرPTHREAD_CREATE_DETACHED. يتم ضبط هذه المميزة باستخدام التابع

pthread_attr_{set|get}detachstate() ;

  • Stack base address: يمكن التحكم في عنوان بدأ المكدس في الذاكرة من خلال التابع

pthread_attr_{set|get}stackaddr() ;

يعتبر ذلك مفيداً في بناء أنظمة الزمن الحقيقي. القيمة الافتراضية هي NULL و التي تعني أن نظام التشغيل سيقوم بحجز العنوان تلقائياً.

  • Stack size: تسمح بتحديد حجم المكدس من خلال التابع

pthread_attr_{set|get}stacksize() ;

يجب ألا يقل حجم المكدس عن PTHREAD_STACK_MIN و التي تساوي 8kByte، في حال اختيار NULL سيتم حجز الحجم الافتراضي و الذي يختلف باختلاف نظام التشغيل

  • Other scheduling information:

    • Scheduling Policy : تحدد سياسة الجدولة التي سيخضع لها الـ Thread و تكون SCHED_OTHER, SCHED_RR, or SCHED_FIFO ،تختلف القيمة الافتراضية باختلاف نظام التشغيل .

تتم وفق التابع

pthread_attr_{set|get}schedpolicy() ;

  • Scheduling Inheritance :تحدد كيفية حصول الـ Thread على معلومات الجدولة، هل ستحصل عليها من خلال الوراثةPTHREAD_INHERIT_SCHED أم من خلال باقي المميزات PTHREAD_EXPLICIT_SCHED ، تختلف القيمة الافتراضية باختلاف نظام التشغيل. يتم ذلك من

pthread_attr_{set|get}inheritsched() ; خلال التابع

  • Scheduling Parameters : تحدد كيف يتم جدولة الـ Thread و تحدد أولويتها. نستخدم التابع

pthread_attr_{set|get}schedparam() ;

نستخدم التابع للتهيئة بالقيم الافتراضية

pthread_attr_init(pthread_attr_t *attr);

طرق حماية المنطقة الحرجة:

1- الإقصاء المتبادل :

لتحقيق حماية المتحولات المشتركة المنطقة الحرجةباستخدام الإقصاء المتبادل يجب

  • تعريف متحول من نوعpthread_mutex_t.

  • تهيئة المتحول و تكون قيمة المميز NULL لأجل استخدام القيم الافتراضية.

pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; أو من خلال

  • لحماية المنطقة الحرجة نستخدم التوابع التالية

  • pthread_mutex_lock(pthread_mutex_t *mutex );

تقوم بقفل المتحول mutex و الدخول للمنطقة الحرجة، إن كان المتحول مقفل مسبقاً فإن الـ Thread سيتم إبعادها block حتى يتم فك قفل المتحول(تحرير) mutex.

  • pthread_mutex_unlock(pthread_mutex_t *mutex);

يتم فك قفل المتحول mutex و تنبيه أول Thread تم إبعاده.

  • pthread_mutex_trylock(pthread_mutex_t *mutex);

مطابقة تماماً لسابقتها سوى أنها عندما يكون المتحول mutex مقفلاً فأنها تعود مباشرة بعد الاستدعاء بالقيمة EBUSY، تعتبر مفيدة في تفادي بعض حالات Deadlock.

مثال و مقارنة:

#include<stdio.h>

#include<stdlib.h>

#include<pthread.h>

//global varble has value zero this counter was shared from f1,f2

int counter=0;

//pthread_mutex_t m;

void *f1(void *x)

{

int i;

printf(“thread 1 started !\n”);

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

{

//pthread_mutex_lock(&m);

counter++;

//pthread_mutex_unlock(&m);

}

printf(“end of thread 1\n”);

return NULL;

}

void *f2(void *x)

{

int i;

printf(“thread 2 started !\n”);

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

{

//pthread_mutex_lock(&m);

counter–;

//pthread_mutex_unlock(&m);

}

printf(“end of thread 2\n”);

return NULL;

}

int main (int argc , char *argv[])

{

pthread_t t1,t2;

//pthread_mutex_init(&m,NULL);

printf(“Hello word!\n”);

pthread_create (&t1,NULL,f1,NULL);

pthread_create(&t2,NULL,f2,NULL);

printf(“the main has started the two thread.it will wait\n”);

pthread_join(t1,NULL);

pthread_join(t2,NULL);

printf(“the final value of counter is %d\n”,counter);

return 0;

}

الخرج بدون حماية:

Hello word!

thread 1 started !

the main has started the two thread.it will wait

thread 2 started !

end of thread 2

end of thread 1

the final value of counter is 443857

الخرج مع تطبيق الإقصاء المتبادل:

Hello word!

thread 1 started !

the main has started the two thread.it will wait

thread 2 started !

end of thread 1

end of thread 2

the final value of counter is 0

2- الانتظار:

قد تضطر إحدى الـ Threadو لتكن th1 لانتظار Thread أخرىth2 حتى انتهاءth2 كامل عملها، لذلك تستدعي th1

pthread_join(pthread_t thread, void **status); التابع

thread : الذي يتم انتظاره أي th2.

Status : الحالة التي يتم إعادتها من th2 بعد إنتهاء عمله(Terminated). إذا تم تمريرNULL لا يتم قراءة الحالة.

ملاحظة: يفترض أن تكون th2 تملك المميزةPTHREAD_CREATE_JOINABLE.

مثال:

#include<stdio.h>

#include<stdlib.h>

#include<pthread.h>

//global varble shared from f1,f2

int image[2][5];

void *f1(void *x)

{

int i;

printf(“thread 1 started !\n”);

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

{

image[0][i]++;

}

printf(“end of thread 1\n”);

return NULL;

}

void *f2(void *x)

{

int i;

printf(“thread 2 started !\n”);

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

{

image[1][i] *= 2;

}

printf(“end of thread 2\n”);

return NULL;

}

int main (int argc , char *argv[])

{

int i,j;

pthread_t t1,t2;

//pthread_mutex_init(&m,NULL);

printf(“Inialization!\n”);

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

for (j=0 ; j<5 ; j++)

image[i][j] = i+j;

pthread_create (&t1,NULL,f1,NULL);

pthread_create(&t2,NULL,f2,NULL);

printf(“the main has started the two thread.it will wait\n”);

pthread_join(t1,NULL);

pthread_join(t2,NULL);

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

{

for (j=0 ; j<5 ; j++)

{

image[i][j] – – ;

printf(“%d\t”,image[i][j]);

}

printf(“\n”);

}

return 0;

}

3- المتحولات الشرطية:

ترتبط المتحولات الشرطية دائماً بمتحول إقصاء متبادل و ذلك لتجنب شروط التسابق التي قد تحصل بالشكل التالي: قبل دخول th1 في حالة انتظار يحصل تبديل سياق و يقوم th2 بإرسال إشارة لتنبيه th1 التي من المفترض أنها في حالة انتظار، بعد ذلك – عند تبديل سياق آخرتعود th1 لتدخل في حالة انتظارلإشارة من th2 و التي لن تصل وبالتالي تبقى th1 في حالة انتظار دائم!

لتحقيق حماية المتحولات المشتركة المنطقة الحرجةباستخدام المتحول يجب:

  • تعريف متحول من نوع pthread_cond_t.

  • تهيئة المتحول و تكون قيمة المميز NULL لأجل استخدام القيم الافتراضية.

    pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

أو من خلال:

pthread_cond_t condition = PTHREAD_COND_INITIALIZER;

  • لحماية المنطقة الحرجة نستخدم التوابع التالية

 
  • pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex );

يسبق هذه التابع دائماً تابع قفل لمتحول اﻹقصاء المتبادل mutex ، يقوم هذا التابع بفك قفل (تحرير) المتحول mutex و دخول الـ Thread بحالة انتظار مرتبطة بالمتحول الشرطي cond.

  • pthread_cond_signal(pthread_cond_t *cond );

يسبق هذه التابع دائماً تابع قفل لمتحول اﻹقصاء المتبادل mutex الذي تم التعامل معه في التابع pthread_cond_wait ، يقوم بإرسال إشارة(يوقظ) لأول Thread في حالة انتظار مرتبط بالمتحول cond.

مثال:

#include <stdio.h>

#include <stdlib.h>

#include <pthread.h>

pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;

pthread_cond_t condition_var = PTHREAD_COND_INITIALIZER;

void *functionCount1();

void *functionCount2();

int count = 0;

#define COUNT_DONE 10

#define COUNT_HALT1 3

#define COUNT_HALT2 6

main()

{

pthread_t thread1, thread2;

pthread_create( &thread1, NULL, &functionCount1, NULL);

pthread_create( &thread2, NULL, &functionCount2, NULL);

pthread_join( thread1, NULL);

pthread_join( thread2, NULL);

printf("Final count: %d\n",count);

exit(0);

}

// Write numbers 1-3 and 8-10 as permitted by functionCount2()

void *functionCount1()

{

for(;;)

{

// Lock mutex and then wait for signal to relase mutex

pthread_mutex_lock( &count_mutex );

// Wait while functionCount2() operates on count

// mutex unlocked if condition varialbe in functionCount2() signaled.

pthread_cond_wait( &condition_var, &count_mutex );

count++;

printf("Counter value functionCount1: %d\n",count);

pthread_mutex_unlock( &count_mutex );

if(count >= COUNT_DONE) return(NULL);

}

}

// Write numbers 4-7

void *functionCount2()

{

for(;;)

{

pthread_mutex_lock( &count_mutex );

if( count < COUNT_HALT1 || count > COUNT_HALT2 )

{

// Condition of if statement has been met.

// Signal to free waiting thread by freeing the mutex.

// Note: functionCount1() is now permitted to modify "count".

pthread_cond_signal( &condition_var );

}

else

{

count++;

printf("Counter value functionCount2: %d\n",count);

}

pthread_mutex_unlock( &count_mutex );

if(count >= COUNT_DONE) return(NULL);

}

}

الخرج:

Counter value functionCount1: 1

Counter value functionCount1: 2

Counter value functionCount1: 3

Counter value functionCount2: 4

Counter value functionCount2: 5

Counter value functionCount2: 6

Counter value functionCount2: 7

Counter value functionCount1: 8

Counter value functionCount1: 9

Counter value functionCount1: 10

Final count: 10

ملاحظة:إن  كل التوابع التي مرّت في المحاضرة تعيد قيمة صحيحة int تعبّر عن رقم الخطأ في حال حدوثه أو تعيد القيمة 0 في حال تم استدعاء التابع و تنفيذه بشكل سليم.

أمثلة:

مثال1:

لدينا في نظام ما حساس Sensor و هناك أربعة أجزاء من النظام يقومون بقراءة الحساس و يجمع كل جزء على حدى مجموع القراءات التي حصل عليها، النظام الكامل يقوم بعرض مجموع كامل القراءات التي تمت.

نعبر عن قراءة قيمة الحساس بتوليد رقم عشوائي و سنمثل أجزاء الدارة بـfour threads و سيكون لدينا متحول عام g_sum للاحتفاظ بمجموع كامل القراءات – يمثل منطقة حرجة- و متحول محلي sum لكل Thread للاحتفاظ بمجموع القراءات التي قام بها الـ Thread.

#include<stdio.h>

#include<stdlib.h>

#include<pthread.h>

int g_sum = 0 ;

pthread_mutex_t mutex;

int rand_Generator()

{

int X = rand();

return 2 * (X % 10 + 1);

}

void *readSencor(void *Z)

{

int sum = 0 , temp;

int i ;

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

{

temp = rand_Generator();

sum+= temp;

pthread_mutex_lock(&mutex);

g_sum+= temp;

pthread_mutex_unlock(&mutex);

}

printf("Local Sum = %d \n",sum);

}

int main(int argc, char *argv[])

{

pthread_t thr1, thr2, thr3, thr4;

pthread_mutex_init(&mutex, NULL);

pthread_create(&thr1, NULL, readSencor, NULL);

pthread_create(&thr2, NULL, readSencor, NULL);

pthread_create(&thr3, NULL, readSencor, NULL);

pthread_create(&thr4, NULL, readSencor, NULL);

pthread_join(thr1, NULL);

pthread_join(thr2, NULL);

pthread_join(thr3, NULL);

pthread_join(thr4, NULL);

printf("Global Sum = %d\n",g_sum);

return EXIT_SUCCESS;

}

الخرج:

Local Sum = 11000

Local Sum = 11076

Local Sum = 11054

Local Sum = 10660

Global Sum = 43790

إن لم يتم حماية المتحول g_sum فلن يكون مجموع القراءات الأربعة مساوياً لقيمة g_sum!

مثال 2 :

لدينا جسر يصل بين دمشق و حلب (جسر الرستن) و نريد بناء نظام زمن حقيقي لتنظيم السير بين السيارات المتجهة لدمشق و السيارات المتجهة لحلب.

يتميز هذا الجسر بأنه جسر وحيد الاتجاه أي لايمكن مرور سيارتين باتجاهين مختلفين، و أن حدود استطاعته هي 3 سيارات في لحظة واحدة.

لتحقيق التوازن بين الاتجاهين سنسمح بمرور خمس سيارات فقط بكل اتجاه ثم نقوم بعكس الاتجاه لنسمح لخمس سيارت من الجهة الأخرى بالمرور.

كل سيارة قادمة إلى الجسر ستواجه إحدى الحالات التالية:

  • الطريق مفتوح و عدد السيارات فوق الجسر أقل من 3 ==> تعبر.

  • الطريق مفتوح و عدد السيارات فوق الجسر أكثر من 3 ==> تنتظر زمن عشوائي و تعاود الاختبار.

  • الطريق مغلق -مفتوح بالاتجاه الآخر- و لكن الجسر فارغ ==> تبديل اتجاه السير و تعبر.

  • الطريق مغلق و الجسر غير فارغ ==> تنتظر و تعاود الاختبار.

الزمن اللازم لكل سيارة كي تقطع الجسر سنعبر عنه بتعليمة sleep لزمن عشوائي. سنعبر عن السيارات التي تتجهة إلى حلب بـ Thread و كذلك السيارات التي تتجه إلى دمشق، كل إعاداة تنفيذ للتابع يعني قدوم سيارة جديدة.

لتحقيق محاكاة أكثر واقعية فيجب ربط أكثر من six threads بكل طريق و لكن للتبسيط ربطنا thread واحد بكل اتجاه.

ملاحظة:التابع sleep يأخذ الزمن بالثانية في Linux بينما في Windows يأخذ الزمن بـ ms.

#include <stdio.h>

#include <pthread.h>

#include <stdlib.h>

//Global variables

int direction = 2; // direction = 2 the way to Damascus ; direction = 1 the way to Aleppo

int numberOfCars = 0; // Cars on the bridge

int queue = 0; // total cars allowed to across the bridge in one way

const int maxQueueCount = 5;

long randomInt(int min, int max)

{

if (min > max)

return max + (int)((min - max + 1) * rand() / (RAND_MAX + 1.0));

else

return min + (int)((max - min + 1) * rand() / (RAND_MAX + 1.0));

}

pthread_mutex_t mutex;

void * carToAelppo(void *x)

{

while (1)

{

if ((direction == 2) && numberOfCars == 0)

{

pthread_mutex_lock(&mutex);

direction = 1;

queue = 0;

queue++;

numberOfCars++;

pthread_mutex_unlock(&mutex);

printf("I'm going to Aeleppo\n");

sleep(randomInt(2, 4));

pthread_mutex_lock(&mutex);

numberOfCars--;

pthread_mutex_unlock(&mutex);

continue;

}

else if (numberOfCars >= 0 && numberOfCars < 3 && direction == 1)

{

pthread_mutex_lock(&mutex);

queue++;

numberOfCars++;

pthread_mutex_unlock(&mutex);

printf("I'm going to Aleppo\n");

sleep(randomInt(2, 4));

pthread_mutex_lock(&mutex);

numberOfCars--;

pthread_mutex_unlock(&mutex);

continue;

}

else if (numberOfCars >= 3 && direction == 1)

{

sleep(randomInt(2, 4));

continue;

}

else if (direction == 2 && queue >= maxQueueCount)

{

pthread_mutex_lock(&mutex);

direction = 1;

queue = 0;

printf("I'll stop Damascus's way!\n");

pthread_mutex_unlock(&mutex);

continue;

}

else

{

sleep(randomInt(2, 4));

continue;

}

}

sleep(randomInt(1, 3));

return NULL;

}

void * carToDamascus(void *x)

{

while (1)

{

if ((direction == 1) && numberOfCars == 0)

{

pthread_mutex_lock(&mutex);

direction = 2;

queue = 0;

queue++;

numberOfCars++;

pthread_mutex_unlock(&mutex);

printf("I'm going to Dumascus\n");

sleep(randomInt(2, 4));

pthread_mutex_lock(&mutex);

numberOfCars--;

pthread_mutex_unlock(&mutex);

continue;

}

else if (numberOfCars >= 0 && numberOfCars < 3 && direction == 2)

{

pthread_mutex_lock(&mutex);

queue++;

numberOfCars++;

pthread_mutex_unlock(&mutex);

printf("I'm going to Damascus\n");

sleep(randomInt(2, 4));

pthread_mutex_lock(&mutex);

numberOfCars--;

pthread_mutex_unlock(&mutex);

continue;

}

else if (numberOfCars >= 3 && direction == 2)

{

sleep(randomInt(2, 4));

continue;

}

else if (direction == 1 && queue >= maxQueueCount)

{

pthread_mutex_lock(&mutex);

direction = 2;

queue = 0;

printf("I'll stop Aleppo's way!\n");

pthread_mutex_unlock(&mutex);

continue;

}

else

{

sleep(randomInt(2, 4));

continue;

}

}

sleep(randomInt(1, 3));

return NULL;

}

int main(int argc, char *argv[])

{

int rc;

pthread_t thr1, thr2;

pthread_mutex_init(&mutex, NULL);

rc = pthread_create(&thr1, NULL, carToAelppo, NULL);

rc = pthread_create(&thr2, NULL, carToDamascus, NULL);

rc = pthread_join(thr1, NULL);

rc = pthread_join(thr2, NULL);

return EXIT_SUCCESS;

}

Output:

I'm going to Damascus

I'm going to Damascus

I'm going to Damascus

I'm going to Damascus

I'm going to Damascus

I'll stop Damascus's way!

I'm going to Aleppo

I'm going to Aleppo

I'm going to Aleppo

I'm going to Aleppo

I'm going to Aleppo

I'll stop Aleppo's way!

I'm going to Damascus

..etc

Home Work

Hospital -

نريد محاكاة وصول مرضى إلى غرفة الإسعاف في مشفى، كل مريض يمر بالمراحل التالية:

تسجيل الدخول – الفحص – المعالجة – إعادة الفحص – تسجيل خروج

يتوفر في المشفى عدة مصادر:

أطباء – ممرضات – أسرّة – موظفون

المرحلة

المصادر

زمن التنفيذ(ثانية)

تسجيل الدخول

موظف

4

الفحص

ممرضة + سرير

6

المعالجة

طبيب + سرير

10

إعادة الفحص

ممرضة + سرير

5

تسجيل خروج

موظف

2

توجيه:

  • كل مريض نعبر عنه بـ Thread و لتحقيق محاكاة جيدة يفضل إنشاء ten threads تعبر عن عشرة مرضى.

  • عند حجز السرير في المرحلة الثانية لا يتم التخلي عنه حتى نهاية المرحلة الرابعة.

 

About زين العابدين

مهندس حواسيب - معهد IDA - جامعة Braunshweig التقنية.
هذا المنشور نشر في دروس تعليمية وكلماته الدلالية , , , , , , , , , , . حفظ الرابط الثابت.

2 ردان على الدرس الثاني : ‫حماية المنطقة الحرجة‬

  1. جزاك الله ألف خير وبارك فيك
    وأتمنى لو تعمل كتاب بهذا الشرح الممتع

  2. يقول عبدالله:

    السلام عليكم
    ماهو ال test and set
    وماذا نعني بانه hardware????
    ,a;vh

أضف تعليقاً

إملأ الحقول أدناه بالمعلومات المناسبة أو إضغط على إحدى الأيقونات لتسجيل الدخول:

WordPress.com Logo

أنت تعلق بإستخدام حساب WordPress.com. تسجيل خروج   / تغيير )

صورة تويتر

أنت تعلق بإستخدام حساب Twitter. تسجيل خروج   / تغيير )

Facebook photo

أنت تعلق بإستخدام حساب Facebook. تسجيل خروج   / تغيير )

Google+ photo

أنت تعلق بإستخدام حساب Google+. تسجيل خروج   / تغيير )

Connecting to %s