این جلسه، تیم کدگیت را با آموزش سمافور در جاوا همراهی کنید. این آموزش ادامه Multithreading است. اگر آموزش های قبل را مطالعه نکردید پیشنهاد می شود ابتدا آنها را مطالعه و سپس به خواندن این آموزش بپردازید.
مقدمه
در آموزش های قبل در مورد نحوه ایجاد thread و مشکلات آن صحبت شد. همانطور که گفته شد، دو thread همزمان اجرا میشوند نباید اطلاعاتی را به اشتراک بگذارند. اگر اطلاعاتی به اشتراک گذاشتند سپس مشکل در خروجی برنامه پیش می آید به عنوان مثال دو thread همزمان بخواهند روی یک فایل مطلبی را بنویسند!!!!! در این صورت فایل ایجاد شده، خروجی مطلوب ما نیست.
راه حل های مختلفی برای حل این مشکل وجود و هر کدام مزیتی نسبت به دیگری دارد و عیوبی هم دارند!!! برای مشکل بالا راه حل هایی مانند synchronized گفته شد. یکی از راه حلهای دیگر استفاده از سمافور است. قبل از توضیح سمافور نیاز به توضیح کوتاهی در مورد پیش نیاز های آن است.
ناحی بحرانی(critical section)
ناحیه بحرانی قسمتی از کد یک برنامه و ساختاری در برنامهنویسی است و در برنامههایی به کار میرود که پردازشها به حافظه اشتراکی دسترسی دارد. در ناحیه بحرانی پردازش میتواند مانند حالت عادی به تغییر متغییرها، بهروز کردن یک جدول، نوشتن در یک پرونده و … بپردازد اما جنبه مهم آن این است که وقتی یک پردازش در حال اجرای بخش بحرانیاش باشد هیچ پردازش دیگری مجاز نیست که در بخش بحرانی خود اجرا شود و بلوکه میشود بنابراین اجرای پردازهها در بخشهای بحرانی متقابلاً منحصر است.(ویکیپدیا)
راهحلهای بخش بحرانی باید بتوانند به سه نیاز زیر پاسخ دهند(ویکیپدیا):
- انحصار متقابل: اگر پردازشی به بخش بحرانی رسید هیچ پردازه دیگری نتواند در بخش بحرانی اجرا شود.
- پیشروی: اگر پردازشی در ناحیه بحرانی نباشد و پردازشهای مایل به ورود به ناحیه بحرانی داشته باشیم، در این صورت فقط پردازشهایی که در بخش باقیماندیشان نباشند میتوانند در تصمیمگیریای که کدام پردازش میتواند وارد ناحیه بحرانی شود شرکت کنند و این انتخاب نمیتواند نامعین به تعویق بیفتد.
- انتظار مقید: بر روی بارهایی که پردازشهای دیگر اجازه دارند وارد بخش بحرانی شوند بعد از آنکه پردازش تقاضای ورود به بخش بحرانی خود را کرد و قبل از آنکه این درخواست، داده شود، وجود دارد.
سمافور
سمافور از یک متغیر int ساده تشکیل شده و دو متد:
- Acquire
- release
متد acquire زمانی به ما اجازه ورود به ناحیه بحرانی را میدهد که thread دیگری در آن قسمت نباشد. متد release هم اطلاع رسانی میکند که ناحیه بحرانی خالی است یعنی وقتی یک thread متد release را صدا زد thread دیگر که در حالت acquire هست باخبر میشود از خالی بودن ناحیه بحرانی و acquire اجازه ورود به thread دوم میدهد.
acquire (S) {
while (S <= 0);
S--;
}
release (S) {
S++;
}
پیاده سازی سمافور
قبل از توضیح پیاده سازی ابتدا دو متد در جاوا را به شما معرفی میکنم که در این جا استفاده کردیم:
- wait: این متد تا موقعی که متد های notify و notifyall صدا زده نشده اند thread در حال اجرا را متوقف میکند(به خواب میبرد).
- Notify: این متد thread که به خواب رفته (متوقف شده) را بیدار میکند!و باعث اجرای آن میشود.
نوبت به پیاده سازی سمافور در جاوا رسیده است. برای پیاده سازی یک فیلد به نام value داریم برای استفاده در acquire و release. کلاس سمافور به صورت زیر است:
public class Semaphore
{
private int value;
public Semaphore(int value) {
this.value = value;
}
public synchronized void acquire() {
while (value <= 0) {
try {
wait();
}
catch (InterruptedException e) { }
}
value--;
}
public synchronized void release() {
++value;
notify();
}
}
سمافور چند نکته ریز دارد اول اینکه موقع صدا زدن release یا acquire نباید بیشتر از یک thread وارد این متد ها شده باشند(استفاده از synchronized).دوم، موقع اتفاق افتادن release حتما signal با خبر شود(استفاده از wait و notify).در کد بالا همانطور که میبینید شرایط بالا رعایت شده است. در مورد value هم باید گفت موقعی که سمافور ما اجازه دسترسی به بیش از یک thread را بدهد(با توجه به مسئله ای که میخواهید حل کنید) میتوان مقدار آن را بیش یک قرار داد در غیر این صورت مقدار آن را باید یک قرار بدهید.