سمافور چیست؟ – در سیستم عامل و به زبان ساده
«سمافور» (Semaphore) اساساً متغیری صحیح یا همان Integer غیرمنفی است که برای حل «مسئله ناحیه بحرانی» (Critical Section Problem) از طریق ایفای نقش به عنوان یک سیگنال مورد استفاده قرار میگیرد. سمافور مفهومی در سیستم عامل برای «همگامسازی» (Synchronization) «پردازههای همزمان» (Concurrent Process) به حساب میآید. در این مقاله به طور جامع به این سوال پاسخ داده شده است که سمافور چیست و انواع سمافور در سیستم عامل نیز به همراه جزئیات کافی و با مثالهای مختلف شرح داده میشود. همچنین پیادهسازی سمافور به کمک مسئله تولیدکننده-مصرفکننده نیز در این مطلب انجام شده است و به سمافور در جاوا و همگامسازی سمافور در پایتون نیز پرداختهایم.
سمافور چیست ؟
در حوزه «همگامسازی پردازهها» (Process Synchronization) مسئلهای وجود دارد به نام «مسئله بخش بحرانی» که برای پردازهها یا همان پروسههای همزمان رخ میدهد و سمافور در این حیطه کاربرد دارد. اما برای اینکه به طور دقیق و جامع بدانیم سمافور چیست ابتدا باید به شرح مفاهیم مرتبط با آن بپردازیم که به نوعی پیشنیاز محسوب میشوند.
پردازه های همزمان در سیستم عامل چیست ؟
پردازههای همزمان، پردازهها یا پروسسهایی هستند که به طور همزمان یا به صورت موازی اجرا میشوند و ممکن است به سایر پروسهها هم وابستگی نداشته باشند.
همگام سازی پردازه چیست ؟
همگامسازی پردازهها به هماهنگی میان دو پروسه یا همان پردازه گفته میشود که طی آن به عناصری نظیر بخشهای مشترکی از کُدها، منابع یا دادهها و سایر موارد دسترسی وجود دارد. برای مثال، ممکن است منبعی وجود داشته باشد که به وسیله ۳ پروسه متفاوت به اشتراک گذاشته شده است و هیچ یک از پردازهها نمیتوانند در یک زمان مشخص منبع مربوطه را تغییر دهند؛ زیرا این کار ممکن است نتایج سایر پردازههایی را تغییر دهد که از همین منبع استفاده میکنند.
ناحیه بحرانی چیست ؟
حالا برای پروسههای همزمان به عملیات همگامسازی پردازهها نیاز است. برای هر تعدادی از پردازشهایی که به طور همزمان اجرا میشوند، مثلاً فرض میکنیم همه آنها نیاز دارند به بخشی از کدها دسترسی پیدا کنند. به این بخش، «Critical Section» یا همان «ناحیه بحرانی» میگویند.
اکنون که با مفاهیم و اصطلاحات پیشنیاز برای پاسخ به سوال سمافور چیست آشنا شدهایم، برای درک بهتر چیستی سمافور میتوانیم به سراغ شرح ضرورت و علت نیاز به سمافورها در سیستم عامل همراه با یک مثال برویم.
ضرورت نیاز به سمافور چیست همراه با مثال
فرض میکنیم ۲ پروسه داریم که همزمان هستند و چون داریم راجع به همگامسازی پروسهها صحبت میکنیم، مثلاً فرض میکنیم آنها متغیری دارند با نام «Shared » که مقدار آن برابر با ۵ است. هدف در اینجا چیست؟ میخواهیم به «انحصار متقابل» (Mutual Exclusion) دست پیدا کنیم، یعنی میخواهیم از دسترسی همزمان به یک منبع مشترک و به اشتراکگذاری شده جلوگیری کنیم. در اینجا این منبع همان متغیرShared با مقدار ۵ است. ابتدا این متغیر را تعریف میکنیم.
1int shared = 5
حالا پروسه شماره یک را به صورت زیر مینویسیم:
1int x = shared; // storing the value of shared variable in the variable x
2x++;
3sleep(1);
4shared = x;
پروسه یا پردازه دوم هم در ادامه آمده است:
1int y = shared;
2y--;
3sleep(1);
4shared = y;
ابتدا با اجرای پروسه شماره یک شروع میکنیم. در این پروسس، متغیرx اعلان شده است که در ابتدا دارای مقدار متغیرSharedیعنی مقدار ۵ است. سپس، مقدارxافزایش مییابد و مقدارش برابر با ۶ میشود و پس از آن هم پروسه به وضعیت استراحت میرود. به دلیل اینکه پردازشهای فعلی همزمان هستند، CPU صبر نمیکند و پردازش پروسه شماره ۲ را آغاز خواهد کرد. متغیر عدد صحیحy دارای مقدار متغیرShared که بدون تغییر باقی مانده و مقدارش همچنان برابر با ۵ است.
سپس مقدارy در پروسه شماره ۲ کاهش داده میشود و پس از آن این پروسه در وضعیت استراحت قرار میگیرد. اکنون سیستم عامل به پردازه شماره یک بازمیگردد و مقدار متغیرSharedبرابر با ۶ میشود. وقتی که این پروسه تکمیل شد، در پردازه شماره ۲، مقدار متغیرSharedبه ۴ تغییر داده میشود.
ممکن است اینطور برداشت شود که اگر عددی را کم یا زیاد کنیم، مقدار آن باید بدون تغییر بماند و این دقیقاً همان چیزی بود که در این دو پردازه داشت اتفاق میافتاد، اما پس از آن، مقدار متغیرSharedبه ۴ تغییر یافت که این دلخواه ما نیست.
مثلاً اگر منبعی با مقدار ۵ داشته باشیم و یک پردازه از آن استفاده کند و مقدار آن را درست مثل مثال مربوط به پروسه X، یک واحد کاهش دهد و همچنین اگر پروسه Y دیگری همان منبعی را آزاد کند که قبلاً دریافت کرده بود، ممکن است شرایط مشابهی رخ دهد و نتیجه آن ۴ خواهد بود که در واقع باید همان ۵ میبود.
به این شرایط «وضعیت رقابتی» (Race Condition) گفته میشود و بنا بر این شرایط، ممکن است مشکلات و مسائلی نظیر «بنبست» (Deadlock) اتفاق بیوفتد. با توجه به اینکه نیاز به همگامسازی مناسبی میان پردازهها وجود دارد و به منظور جلوگیری از وقوع چنین مشکلاتی، از یک متغیر عدد صحیح سیگنال به نام سمافور استفاده میشود.
تعریف سمافور چیست ؟
برای اینکه بخواهیم به طور رسمی سمافور را تعریف کنیم، میتوان گفت سمافور متغیر عدد صحیحی از نوع integer است که به گونهای متقابل و منحصربهفرد به وسیله پردازههای همزمان مورد استفاده قرار میگیرد تا بتوان به همگامسازی موردنظر دست پیدا کرد.
چون سمافورها متغیرهایی از نوع Integer هستند، مقدار ذخیره شده در آنها به عنوان یک سیگنال عمل میکند و این سیگنال به منظور دسترسی به بخش حیاتی کدها یا سایر منابع خاص یک پردازه، مجوز صادر میکند یا به پردازه اجازه دسترسی نمیدهد. اکنون با توجه به اینکه سوال سمافور چیست را تا حد امکان به طور جامع پاسخ دادیم، حال در ادامه به انواع سمافور خواهیم پرداخت، اما پیش از آن مجموعه دورههای آموزش مهندسی کامپیوتر نرمافزار به علاقهمندان معرفی شده است.
معرفی فیلم های آموزش مهندسی و علوم کامپیوتر
مفهوم سمافور در سیستم عامل و برنامه نویسی مطرح میشود و اکثر افرادی که به دنبال پاسخ این پرسش هستند که سمافور چیست میتوانند مباحث مرتبط و مشابه با این مفهوم را در مجموعه آموزشهای مهندسی و علوم کامپیوتر فرادرس بیابند. به طور کلی آموزشهای مرتبط با یکدیگر و دورههایی که پیرامون یک موضوع اصلی خاص ساخته شدهاند را در پلتفرم فرادرس در قالب مجموعههای آموزشی مختلفی گردآوری کردهایم. یکی از این مجموعهها، همین مجموعه آموزشهای مهندسی و علوم کامپیوتر است.
در این مجموعه، دورههای آموزشی متعددی برای اکثر مباحث مختلف مربوط به مهندسی نرم افزار وجود دارند و برای مفاهیم سیستم عامل نیست چند دوره موجود است. همچنین در خصوص برنامه نویسی هم برای زبانهای مختلف و روشها و رویکردهای گوناگون برنامه نویسی در این مجموعه دورههای بسیاری در دسترس علاقهمندان قرار دارند. در تصویر فوق تنها تعدادی از مرتبطترین دورههای این مجموعه با مطلب سمافور چیست آمده است.
- برای مشاهده همه فیلم های آموزش مهندسی و علوم کامپیوتر فرادرس + اینجا کلیک کنید.
انواع سمافور چیست ؟
به طور کلی ۲ نوع سمافور یا در واقع ۲ نوع متغیر Integer سیگنال دهنده وجود دارد که در ادامه فهرست شدهاند و سپس هر کدام در زیربخشهای جداگانه شرح داده میشوند.
- «سمافورهای دودویی» (Binary Semaphores)
- «سمافورهای شمارشی» (Counting Semaphores)
سمافور دودویی چیست ؟
در این نوع از سمافورها، مقدار عدد صحیح سمافور تنها میتواند صفر یا یک باشد. در صورتی که مقدار سمافور یک باشد، به این معنی است که پردازه میتواند به «ناحیه بحرانی» (Critical Section) وارد شود. منظور از ناحیه بحرانی همان ناحیه مشترکی است که باید به آن دسترسی پیدا کند.
اما اگر مقدار سمافور صفر باشد، آنگاه پردازه نمیتواند به راه خود ادامه دهد و وارد ناحیه بحرانی در کُدها بشود. وقتی پردازهای از ناحیه بحرانی کدها استفاده میکند، مقدار سمافور را به صفر تغییر میدهیم و وقتی که پردازهای از آن استفاده نمیکند، میتوانیم به پردازه اجازه دهیم تا به ناحیه بحرانی دسترسی داشته باشد و برای این کار، مقدار سمافور را به یک تغییر میدهیم. به سمافور دودویی یا باینری، «Mutex Lock» هم گفته میشود.
سمافور شمارشی چیست ؟
سمافورهای شمارشی اعداد صحیح سیگنال دهندهای هستند که میتوانند هر مقدار صحیحی را به خود بگیرند. با استفاده از این سمافورها، میتوان در دسترسی به منابع هماهنگی به وجود آورد و در اینجا سمافور شمارنده تعداد منابع در دسترس یا تعداد پردازههای مجاز به استفاده از منابع را مشخص میکند. اگر مقدار سمافور هر مقداری بیشتر از صفر داشته باشد، پروسهها میتوانند به نواحی بحرانی یا منابع مشترک دسترسی داشته باشند.
مقدار سمافور مشخص کننده تعداد پردازههایی است که میتوانند به منابع یا کدها دسترسی داشته باشند. اما اگر مقدار این نوع سمافور برابر با صفر باشد، بدین معنا است که هیچ منبعی وجود ندارد که در دسترس باشد یا ناحیه بحرانی همین حالا هم تحت دسترسی تعدادی از پروسسها هست و نمیتواند به وسیله پروسههای بیشتری مورد دسترسی قرار بگیرد. سمافورهای شمارشی به طور کلی زمانی مورد استفاده قرار میگیرند که تعداد نمونهسازیهای یک منبع بیشتر از یک باشد و چندین پروسه میتوانند به آن منبع دسترسی پیدا کند.
مثال سمافور
اکنون که میدانیم سمافور چیست وبا انواع سمافور هم آشنا شدیم، لازم است نحوه عملکرد سمافور و انواع آن را هم بهتر درک کنیم. همانطور که پیشتر بیان شد، هدف ما همگامسازی پردازهها و فراهمسازی انحصار متقابل در ناحیه بحرانی کدها است. بنابراین باید ساز و کاری را با استفاده از سیگنال Integer سمافور فراهم کنیم تا اجازه ندهیم بیش از یک پردازه به ناحیه بحرانی دسترسی پیدا کند.
1shared variable semaphore = 1;
2process i
3 begin
4 .
5 .
6 P(mutex);
7 execute Critical Section
8 V(mutex);
9 .
10 .
11 end;
اینجا در این قطعه شبهکد، در خط اول یک سمافور تعریف شده است که با عدد یک مقداردهی اولیه میشود. سپس اجرای پروسهiآغاز میشود و بعد همانطور که ملاحظه میکنید، تابعPفراخوانی میشود که مقدار mutex یا همان سمافور را به عنوان ورودی دریافت میکند و سپس به ناحیه بحرانی وارد میشویم که به دنبال آن تابعV اجرا میشود که آن هم مقدار mutex یا همان سمافور را به عنوان ورودی دریافت میکند. پس از آن، باقی کدها اجرا میشوند و پردازه تمام میشود.
باید به یاد داشته باشیم که سمافور متغیری سیگنال دهنده به حساب میآید و اینکه آیا پردازه میتواند به ناحیه بحرانی وارد شود یا خیر به مقدار آن بستگی دارد. در سمافورهای باینری و شمارشی مقدار سمافور را بسته به منابع در دسترس، تغییر میدهیم. به این ترتیب حالا بهتر است به سراغ توابعP وV در شبهکد بالا برویم و آنها را بیشتر توضیح دهیم.
عملیات انتظار و سیگنال در سمافور چیست ؟
عملیات «انتظار» (Wait) و «سیگنال» (Signal) در سمافورها در واقع همان توابعP وV هستند که در شبهکد بالا از آنها استفاده شد. در این بخش ابتدا به شرح عملیات انتظار میپردازیم و سپس به سراغ عملیات سیگنال میرویم.
عملیات انتظار در سمافور چیست ؟
«عملیات انتظار» (Wait Operation) که به آن «تابع P»، «خوابرفتگی» (Sleep)، «کاهش» (Decrease) یا «عملیات تقلیل» (Down Operation) هم میگویند، عملیاتی در سمافور است که ورود یک پردازه به ناحیه بحرانی را کنترل میکند. اگر مقدار mutex یا همان سمافور مثبت باشد، آنگاه مقدار سمافور را کاهش و اجازه میدهیم پردازه به ناحیه بحرانی وارد شود.
باید در نظر داشته باشیم که این تابع یا عملکرد نه پس از آن فراخوانی میشود که پروسه وارد ناحیه بحرانی شده است، بلکه فراخوانی این تابع تنها قبل از ورود به ناحیه بحرانی رخ میدهد. در ادامه شبهکدی برای پیادهسازی این تابع ارائه شده است.
1P(semaphore){
2 if semaphore is greater than 0
3 then decrement semaphore by 1
4}
عملیات سیگنال در سمافور چیست ؟
عملکرد یا تابعVکه به آن «بیدارباش» (Wake-Up)، «افزایش» (Increase) یا «عملیات ترقی» (Up Operation) هم میگویند، با تابع یا عملکرد سیگنال یکسان است و همانطور که میدانیم، زمانی که یک پردازه از ناحیه بحرانی خارج میشود، باید مقدار سمافور را بهروزرسانی کنیم تا بتوانیم به پردازههای جدید سیگنال بدهیم تا آنها بتوانند به ناحیه بحرانی دسترسی پیدا کنند.
برای بهروزرسانی مقدار سمافور، پس از آنکه پردازش مربوطه از ناحیه بحرانی خارج شد، چون مقدار سمافور را یک واحد در عملیات انتظار کاهش دادهایم، اینجا به سادگی آن را افزایش میدهیم. باید در نظر داشته باشیم که این تابع تنها پس از آنکه پردازه از ناحیه بحرانی خارج میشود افزایش را انجام خواهد داد و نمیتوان افزایش را پیش از آن انجام داد که پروسه وارد ناحیه میشود. در ادامه، تابع V به صورت شبهکد آمده است.
1V(semaphore){
2 increment semaphore by 1
3}
در صورتی که مقدار سمافور در ابتدا برابر با یک باشد، آنگاه در هنگام ورود پردازه به ناحیه بحرانی، تابع انتظار مقدار آن را به صفر کاهش خواهد داد و این یعنی هیچ پردازه دیگری نمیتواند به ناحیه بحرانی دسترسی پیدا کند و به این طریق از انحصار یا ممانعت متقابل (تنها در سمافورهای باینری) اطمینان حاصل میشود.
آموزش پیاده سازی سمافورهای دودویی و شمارشی
در این بخش نگاهی به پیادهسازی سمافورهایی با دو پردازهP<span class="englishfont">1</span> وP<span class="englishfont">2</span> خواهیم داشت. برای سادگی مثال ارائه شده در این بخش، تنها با دو پردازه کار خواهیم کرد؛ اگرچه در عمل معمولاً سمافورها برای تعداد زیادی از پروسهها مورد استفاده قرار میگیرند.
آموزش پیاده سازی سمافور دودویی
در ابتدا مقدار سمافور برابر با یک است. وقتی که پردازهP<span class="englishfont">1</span>وارد ناحیه بحرانی میشود، مقدار سمافور به صفر تغییر میکند. اگر پردازه P<span class="englishfont">2</span>بخواهد در این مقطع زمانی وارد ناحیه بحرانی شود، امکان آن را نخواهد داشت، زیرا مقدار سمافور بیشتر از صفر نیست.
پردازهP<span class="englishfont">2</span>ناچار است صبر کند تا مقدار سمافور بیشتر از یک شود و این رویداد تنها زمانی اتفاق خواهد افتاد کهP<span class="englishfont">1</span>ناحیه بحرانی را ترک و عملیات سیگنال اجرا شود که باعث افزایش مقدار سمافور میشود. بنابراین انحصار متقابل به این صورت با استفاده از سمافور باینری حاصل میشود، به عبارت دیگر هر دو پردازه نمیتوانند همزمان به ناحیه بحرانی دسترسی پیدا کنند.
کدهای مربوط به پیادهسازی سمافور دودویی در ادامه آمده است:
1struct Semaphore{
2 enum value(0, 1);
3 /* This queue contains all the process control blocks (PCB) of the processes that get blocked while performing the wait operation */
4 Queue<process> processes;
5}
6
7wait (Semaphore mutex){
8 if (mutex.value == 1){
9 mutex.value = 0;
10 }
11 else {
12 // since process cannot access critical section, adding it to waiting queue
13 processes.push(P);
14 sleep();
15 }
16}
17
18signal (Semaphore mutex){
19 if (mutex.processes is empty){
20 mutex.value = 1;
21 }
22 else {
23 // selecting a process from the waiting queue which can next access the critical section
24 process p = processes.pop();
25 wakeup(p);
26 }
27}
در کدهای بالا، یک سمافور دودویی پیادهسازی شده است که امکان انحصار متقابل را فراهم میآورد. اکنون در ادامه به آموزش پیادهسازی سمافور شمارشی پرداخته شده است.
آموزش پیاده سازی سمافور شمارشی
در سمافورهای شمارشی، تنها تفاوت این است که تعداد چند منبع خواهیم داشت یا به بیان دیگر، تعداد مشخصی از پردازهها وجود دارند که میتوانند به طور همزمان و در لحظه یکسان به ناحیه بحرانی دسترسی داشته باشند.
فرض میکنیم منبعی داریم که دارای ۴ «نمونهسازی» (Instance) است، بنابراین مقدار اولیه سمافور به صورت semaphore = 4 خواهد بود. هر وقت پردازهای نیازمند دسترسی به ناحیه یا منبع بحرانی باشد، تابع انتظار را فراخوانی میکنیم و تنها در صورتی که مقدار سمافور بیشتر از صفر باشد، مقدار سمافور را یکی کاهش میدهیم.
وقتی ۴ پردازه به ناحیه بحرانی یا منبع بحرانی دسترسی پیدا کردهاند و پنجمین پردازه هم به آن نیازمند باشد، آن را در صف انتظار قرار میدهیم و تنها زمانی آن را فراخوانی (بیدار) میکنیم که پردازه عملکرد یا تابع سیگنال را اجرا کرده باشد و این یعنی مقدار سمافور یک واحد افزایش پیدا کرده است. کدهای مربوط به پیادهسازی سمافور شمارشی در ادامه آمده است.
1struct Semaphore(){
2 int value;
3 Queue<process> processes;
4}
5
6wait (Semaphore s){
7 s.value -= 1;
8 if (s.value < 0){
9 processes.push(p);
10 block();
11 }
12 else{
13 return;
14 }
15}
16
17signal (Semaphore s){
18 s.value += 1;
19 if (s.value >= 0){
20 process p = processes.pop();
21 wakeup(p);
22 }
23 else{
24 return;
25 }
26}
حل مسئله تولید کننده مصرف کننده با سمافور
اکنون که درکی از نحوه کارکرد سمافورها بدست آمده است، میتوانیم نگاهی به کاربرد سمافورها در زندگی واقعی در مسائل کلاسیک همگامسازی داشته باشیم. مسئله تولیدکننده-مصرفکننده یکی از مسائل کلاسیک همگامسازی پردازهها به حساب میآید.
صورت مسئله تولید کننده مصرف کننده
در صورت مسئله تولیدکننده-مصرفکننده بیان میشود که بافری با اندازه ثابت وجود دارد و تولید کننده آیتمهایی را تولید خواهد کرد و آنها را در بافر قرار خواهد داد. مصرف کننده میتواند آیتمها را از بافر بردارد و آنها را مصرف کند. هدف ما در این مسئله این است که اطمینان حاصل کنیم چه زمانی آیتم مربوطه توسط تولید کننده در بافر قرار داده شده است. در اینجا، بافر ناحیه بحرانی است.
راه حل مسئله تولید کننده مصرف کننده با سمافور
بنابراین برای حل این مسئله، از ۲ سمافور شمارشی با نامهای «full » (به معنی پُر) و «empty » (به معنی خالی) استفاده شده است. سمافور شمارشیfullنظارت و تعقیب تمام اسلاتهای اشغال شده در بافر را بر عهده خواهد داشت یا به عبارت دیگر، پیگیری تمام آیتمهای موجود در بافر را انجام خواهد داد. همچنین، سمافور empty نیز نظارت بر تمام آیتمهای خالی در بافر را بر عهده خواهد داشت و مقدار mutex برابر با1خواهد بود.
در ابتدا، مقدار سمافورfullبرابر با صفر است، چون تمام اسلاتها در بافر اشغال نشدهاند و مقدار بافرemptyهم برابر باn خواهد بود، که در اینجاnاندازه بافر است، چون تمام اسلاتها در ابتدا خالی هستند.
برای مثال، اگر اندازه بافر دربرنامه نویسی برابر با ۵ تعیین شده باشد، آنگاه semaphore full = 0 خواهد بود، زیرا تمام اسلاتها در بافر اشغال نشده هستند وemptyنیز برابر با ۵ است. راهحل استنتاج شده برای بخش تولید کننده در مسئله به صورت زیر است:
1do{
2 // producer produces an item
3 wait(empty);
4 wait(mutex);
5 // put the item into the buffer
6 signal(mutex);
7 signal(full);
8} while(true)
در کدهای فوق، «عملیات انتظار» (Wait Operation) را در سمافورهایemptyو mutex، زمانی فراخوانی میکنیم که تولید کننده آیتمی را تولید میکند. به دلیل اینکه آیتمی تولید شده است، باید در بافر قرار داده شود و تعداد اسلاتهای خالی را یک واحد کاهش دهد، بنابراین، عملیات انتظار را در سمافور خالی فراخوانی میکنیم. همچنین باید مقدارmutexرا هم کاهش دهیم تا از دسترسی مصرف کننده به بافر جلوگیری به عمل آید.
پس از آن، تولید کننده آیتم مربوطه را در داخل بافر قرار داده است و بنابراین میتوانیم مقدار سمافورfullرا یک واحد افزایش دهیم و همچنین مقدارmutexرا هم زیاد کنیم، چون تولید کننده وظیفه خود را انجام داده است و حالا سیگنال امکان این را خواهد داشت تا به بافر دسترسی داشته باشد. راهحل بخش مربوط به مصرف کننده در این مسئله به صورت زیر است.
1do{
2 wait(full);
3 wait(mutex);
4 // removal of the item from the buffer
5 signal(mutex);
6 signal(empty);
7 // consumer now consumed the item
8} while(true)
مصرف کننده نیاز دارد آیتمهای تولید شده توسط تولید کننده را مصرف کند. بنابراین، وقتی که مصرف کننده آیتم را از بافر حذف میکند تا آن را مصرف کند، لازم است مقدار سمافورfullرا یک واحد کم کنیم، چون یک اسلات خالی خواهد شد و همچنین باید مقدارmutexرا کم کنیم تا تولید کننده به بافر دسترسی پیدا نکند.
حالا که مصرف کننده آیتم را مصرف کرده است، میتوان مقدار سمافور خالی را کاهش داد و مقدار سمافور خالی را هم یک واحد کم کرد. به این ترتیب، مسئله تولیدکننده-مصرفکننده را حل کردهایم و کار به اتمام رسیده است. حال در ادامه به شرح مزایای سمافورها پرداختهایم تا به لزوم استفاده از آنها بیشتر آشنا شویم.
مزایای سمافور چیست ؟
همانطور که در طول این مطلب تا اینجا متوجه شدیم، سمافورها کارایی و فایده بسیار زیادی در حوزه همگامسازی پردازهها دارند. در این بخش نیز به طور خلاصه مزایای سمافورها را شرح میدهیم.
- سمافورها پروسسها را یکی یکی به داخل ناحیه بحرانی راه میدهند و همچنین (در خصوص سمافورهای باینری) انحصار متقابل را هم به صورت بسیار دقیق و استوار فراهم میکنند.
- به خاطر شلوغی و انتظار زیاد، هیچ منبعی تلف نخواهد شد، چون با استفاده از سمافورها، زمان پردازنده برای بررسی برقرار شدن شرایط به منظور اجازه دادن به یک پردازه برای دسترسی پیدا کردن به ناحیه بحرانی تلف نخواهد شد.
- کدهای سمافورها در همان کدهای ناحیه مستقل از ماشین در ریزهسته نوشته و پیادهسازی میشوند و بنابراین، سمافورها مستقل از ماشین هستند.
پس از شرح مزایای سمافورها، بهتر است به معایب آنها هم بپردازیم.
معایب سمافور چیست ؟
تا اینجا به بحث راجع به مزایای سمافورها پرداختیم، اما سمافورهای معایبی هم دارند که در ادامه آنها را فهرست کردهایم.
- سمافورها کمی پیچیده هستند و پیادهسازی عملیات انتظار و سیگنال را باید به گونهای انجام دهیم که از بروز بنبست جلوگیری شود.
- استفاده از سمافورها ممکن است باعث بروز «وارونگی اولویت» (Priority Inversion) شود. در وارونگی اولویت، پردازههای دارای اولویت بالا پس از پردازههای با اولویت پایین به ناحیه بحرانی دسترسی پیدا میکنند.
سمافور در جاوا
در این بخش از مطلب سمافور چیست به ارائه آموزشی سریع برای سمافور در جاوا پرداختهایم و مبانی سمافورها در جاوا را شرح دادهایم. کار را باjava.util.concurrent.Semaphore آغاز میکنیم. میتوان از سمافورها برای محدود کردن تعداد نخهایی استفاده کرد که به منبعی خاص دسترسی پیدا میکنند. در مثال زیر یک صف ساده لاگین برای اعمال محدودیت روی تعداد کاربران در سیستم پیادهسازی شده است.
1class LoginQueueUsingSemaphore {
2
3 private Semaphore semaphore;
4
5 public LoginQueueUsingSemaphore(int slotLimit) {
6 semaphore = new Semaphore(slotLimit);
7 }
8
9 boolean tryLogin() {
10 return semaphore.tryAcquire();
11 }
12
13 void logout() {
14 semaphore.release();
15 }
16
17 int availableSlots() {
18 return semaphore.availablePermits();
19 }
20
21}
توجه کنید که چگونه از متُدهای زیر استفاده شده است:
- tryAcquire() : اگر بلافاصله مجوزی در دسترس باشد، مقدارTrue را بازمیگرداند و مجوز را میگیرد. در غیر این صورتFalseرا بازمیگرداند. اماacquire() مجوزی را کسب میکند و تا زمانی که مجوزی در دسترس نباشد، مسدودسازی را انجام میدهد.
- release() : این متد مجوز را آزادسازی میکند.
- availablePermits() : تعداد مجوزهای فعلی در دسترس را بازمیگرداند.
تست برنامه صف لاگین با سمافور در جاوا
برای آزمایش صف لاگین پیادهسازی شده، ابتدا سعی میکنیم تا به محدودیت تعیین شده برسیم و بعد بررسی میکنیم که آیا تلاش بعدی برای ورود یا همان لاگین کردن مسدود خواهد شد یا خیر.
1@Test
2public void givenLoginQueue_whenReachLimit_thenBlocked() {
3 int slots = 10;
4 ExecutorService executorService = Executors.newFixedThreadPool(slots);
5 LoginQueueUsingSemaphore loginQueue = new LoginQueueUsingSemaphore(slots);
6 IntStream.range(0, slots)
7 .forEach(user -> executorService.execute(loginQueue::tryLogin));
8 executorService.shutdown();
9
10 assertEquals(0, loginQueue.availableSlots());
11 assertFalse(loginQueue.tryLogin());
12}
در مرحله بعد بررسی میکنیم آیا هیچ اسلاتی پس از اینکه یک خروج از سیستم اتفاق میافتد در دسترس خواهد بود یا خیر:
1@Test
2public void givenLoginQueue_whenLogout_thenSlotsAvailable() {
3 int slots = 10;
4 ExecutorService executorService = Executors.newFixedThreadPool(slots);
5 LoginQueueUsingSemaphore loginQueue = new LoginQueueUsingSemaphore(slots);
6 IntStream.range(0, slots)
7 .forEach(user -> executorService.execute(loginQueue::tryLogin));
8 executorService.shutdown();
9 assertEquals(0, loginQueue.availableSlots());
10 loginQueue.logout();
11
12 assertTrue(loginQueue.availableSlots() > 0);
13 assertTrue(loginQueue.tryLogin());
14}
آموزش همگام سازی با استفاده از سمافور در پایتون
در Lock و RLock در یک لحظه تنها یک «نخ» (Thread) اجازه اجرا دارد، اما گاهی نیازمند این هستیم که تعداد خاصی از نخها را در لحظه اجرا کنیم. فرض میکنیم در یک لحظه باید اجازه بدهیم ۱۰ عضو به پایگاه داده دسترسی پیدا کنند و تنها ۴ عضو اجازه دارند تا به اتصال شبکه دسترسی داشته باشند. برای مدیریت چنین نیازمندیهایی نمیتوان از مفهوم Lock و RLock استفاده کرد و در چنین مواقعی باید از سمافورها کمک بگیریم.
میتوان از سمافور در پایتون برای محدودسازی دسترسی به منابع به اشتراکگذاری شده دارای ظرفیت محدود استفاده کرد. سمافور در پایتون نیز مفهومی پیشرفته در حوزه همگامسازی یا همان Synchronization به حساب میآید. در این بخش به آموزش همگامسازی با استفاده از سمافور در پایتون پرداخته شده است.
ایجاد شی سمافور در پایتون
ابتدا باید شیئي از سمافور را بسازیم. کد مربوط به این کار در ادامه آمده است.
1object_name = Semaphore(count)
در اینجا «count » تعداد نخهایی به حساب میآید که اجازه دسترسی همزمان به آنها داده شده است. مقدار پیشفرضcount برابر با یک است. وقتی که نخی اجرا میشود، متُدacquire() اجرا خواهد شد و سپس مقدار متغیر count یک واحد کاهش مییابد. همچنین هرگاه نخی متُدrelease() را اجرا میکند، آنگاه مقدار متغیر count یکی اضافه خواهد شد. به عبارت دیگر، هرگاه متُدacquire() فراخوانی شود، مقدار متغیرcount کاهش خواهد یافت و هر گاه متدrelease() فراخوانی شود، مقدار count یک واحد افزایش خواهد یافت.
راه های ایجاد شيئی از سمافور در پایتون
۲ حالت برای ایجاد سمافور در پایتون وجود دارد که در این بخش به هر دو پرداختهایم.
حالت اول: حالت اول ایجاد یک شی سمافور در پایتون به صورت زیر است:
1object_name.Semaphore()
در این حالت، به صورت پیشفرض مقدار متغیرCount برابر با یک است که طبق آن، تنها یک نخ اجازه دارد دسترسی پیدا کند. این حالت استفاده از سمافور در پایتون دقیقاً با روش استفاده از مفهوم Lock یکسان است.
حالت دوم: حالت دوم ایجاد شیئی از سمافور در پایتون به صورت زیر انجام میشود:
1object_name.Semaphore(n)
در این حالت، میتوان در هر لحظه به وسیلهn نخ به یک شی سمافور در پایتون دسترسی پیدا کرد. نخهای باقیمانده باید صبر کنند تا زمانی که سمافور آزاد شود.
مثالی برای آموزش همگام سازی با استفاده از سمافور در پایتون
در این بخش کدهای اجرایی سمافور در پایتون ارائه شده است. توضیحات لازم پس از کدها و تصویر خروجی آنها ارائه شدهاند.
1# importing the modules
2from threading import *
3import time
4
5# creating thread instance where count = 3
6obj = Semaphore(3)
7
8# creating instance
9def display(name):
10
11 # calling acquire method
12 obj.acquire()
13 for i in range(5):
14 print('Hello, ', end = '')
15 time.sleep(1)
16 print(name)
17
18 # calling release method
19 obj.release()
20
21# creating multiple thread
22t1 = Thread(target = display , args = ('Thread-1',))
23t2 = Thread(target = display , args = ('Thread-2',))
24t3 = Thread(target = display , args = ('Thread-3',))
25t4 = Thread(target = display , args = ('Thread-4',))
26t5 = Thread(target = display , args = ('Thread-5',))
27
28# calling the threads
29t1.start()
30t2.start()
31t3.start()
32t4.start()
33t5.start()
خروجی کدهای بالا به صورت تصویر زیر است.
اینجا در مثال فوق، ابتدا نمونهای از کلاس سمافور ایجاد شده که در آن مقدارcount برابر با ۳ است و این یعنی شی سمافور میتواند به وسیله ۳ نخ در لحظه مورد دسترسی قرار بگیرد. سپس متُدdisplay()ساخته شده است که نام نخ را ۵ بار چاپ خواهد کرد.
پس از آن، ۵ نخ ایجاد شده و حالا هر وقت متُدstart()فراخوانی شود، در آن لحظه ۳ نخ اجازه مییابند تا به اشیاء سمافور دسترسی پیدا کنند و بنابراین ۳ نخ اجازه دارند متدdisplay() را در لحظه اجرا کنند، اما در این مثال هر گاه اجرا انجام میدهیم، خروجی نامعمولی دریافت میکنیم، زیرا ۳ نخ دارند متُدdisplay() را همزمان اجرا میکنند.
جمعبندی
در این مطلب سعی شد تا به طور جامع به این پرسش پاسخ داده شود که سمافور چیست. به طور خلاصه، سمافور متغیر عدد صحیحی است که به عنوان یک سیگنال مورد استفاده قرار میگیرد تا به پردازهای اجازه دسترسی به ناحیه بحرانی کدها یا برخی از سایر منابع خاص را بدهد یا چنین اجازهای را ندهد. در این مطلب در خصوص انواع سمافور هم بحث شد و گفتیم ۲ نوع سمافور داریم که یکی سمافور دودویی و دیگری سمافر شمارسی نام دارد. سمافور دودویی فقط مقدارهای صفر یا یک را میتواند داشته باشد، اما سمافور شمارشی میتواند هر مقدار عدد صحیحی را در خود ذخیره کند.
همچنین یه این موضوع هم پرداختیم که در کل ۲ عملیات مختلف در سمافورها وجود دارد، یکی عمل «انتظار» (wait) و دیگری عمل «سیگنال» (Signal) است. در عملیات wait مقدار سمافور کاهش مییابد و در عملیات سیگنال هم مقدار سمافور افزایش داده میشود. به علاوه در این نوشته به این مسئله هم پرداخته شد که تعدادی از مسائل همگامسازی پردازه را میتوانیم با استفاده از سمافورها حل کنیم و در پی آن در ادامه این مطلب نیز به حل مسئله تولیدکننده-مصرفکننده پرداختیم. در ۲ بخش انتهایی این مقاله نیز سمافور در جاوا و همچنین سمافور در پایتون نیز شرح داده شده است.