فرض کنید دو Thread دارید. یکی از این نخ ها مقداری را تغییر میدهد و دیگری بر مبنای تغییر داده شد دستوراتی اجرا میکند. کد زیر را در نظر بگیرید :
public class NoVisibility { private static boolean ready=false; private static int number=0; private static class ReaderThread extends Thread { public void run() { while (!ready) Thread.yield(); System.out.println(number); } } public static void main(String[] args) throws InterruptedException { new ReaderThread().start(); number = 42; ready = true; } } |
نکته : متد yield برای رها کردن پردازنده مورد استفاده قرار میگیرد. درواقع برای مدت کوتاهی پردازنده رها شده و در اولین فرصت مجددا به ترد اختصاص میابد.
هیچ تضمینی وجود ندارد که کد فوق خروجی 42 تولید کند (البته احتمالا اگر آن را اجرا کنید چنین خروحی ای مشاهده میکنید. شاید هم نه). عدم اطمینان از خروجی ای که بدیهی بنظر میرسد از کجا ناشی میشود؟
میدانیم که برنامه های چند نخی درواقع به دو صورت اجرا خواهند شد : 1- به صورت همروند 2- به صورت موازی
در همروند توازی واقعی نداریم. تنها در صورتی توازی واقعی ممکن است داشته باشیم که تعداد هسته ها بیش از یک باشد.
آموزش چند نخی(Multithreading) در زبان برنامه نویسی جاوا[کلیک کنید] : در دوره آموزش پیشرفته جاوا در فصل 12 به مباحث چندنخی (تردها) در زبان برنامه نویسی جاوا پرداخته ایم.
خطای دید (Visibility Problem):
اگر نخست ترد Main بتواند مقدار ready را عوض کند و سپس ReaderThread این مقدار را بخواند. ترد دوم در حلقه ی بی نهایت گیر نمیافتد .در غیر اینصورت ممکن است ReaderThread همواره اجرا گردد. در صورتی که Main Thread نتواند در ابتدا مقدار ready را true کند, ترد دوم مقدار Ready که ابتدا false بوده را در رجیستر های هسته ذخیره میکند و تغییراتی که Main Thread بر روی ready اعمال میکند بر رجیستر های هسته ای که ReaderThread را اجرا میکند منعکس نمیشود. بنابراین دو متغیر Ready داریم یکی در رجیستر های پردازنده اول که مربوط به Main Thread است و دیگری در پردازنده ی دوم که مربوط به ReaderThread است.این اتفاق تحت عنوان Reordering شناخته میشود(یعنی دستورات به ترتیبی که نوشتیم و انتظار داریم اجرا نمیشوند). درواقع تضمینی وجود ندارد که چه زمانی کدام Thread مقدار Ready را میخواند. درواقع ترد دوم ممکن است مقداری که Stale (کهنه) است را ببیند.مشکلی که نخ ها نمیتوانند اخرین مقدار یک متغیر را ببینند Visibility Problem نامیده میشود.
وقتی یک نخ، متغیری را بدون همگام سازی میخواند، ممکن است مقداری کهنه (Stale) ببیند. اما حداقل مقداری را میبند که توسط یک نخ دیگر ذخیره شده و صرفا بروز نیست. درواقع مقداری تصادفی نمیبند. چنین ضمانتی، ضمانت out-of-thin-air نامیده میشود.
چنین ضمانتی یک استثنا دارد و آن هم متغیر های 64 بیت هستند(long, double). عملیات های خواندن و نوشتن در مقادیر 64 بیتی که volatile نیستند بصورت دو خواندن و نوشتن مجزای 32 بیتی انجام میشوند(در زمانی که جاوا نوشته شد بیشتر پردازنده ها بصورت غیر بهینه عملیات بر روی متغیر های 64 بیتی را ارائه میدادند). در این حین اگر دو ترد داشته باشیم که یکی میخواند و دیگری مینویسد، ممکن است عملیات نوشتن در 32 بیت اول انجام شود و خواندن در همین حین انجام شده و سپس 32 بیت بعدی نوشته شوند در اینصورت مقداری مشاهده خواهیم کرد که کاملا غیر منتظره و گویی تصادفی است.
اتمیک بودن (Atomicity)
عملیات اتمیک یعنی عملیاتی که بدون دستکاری نخ های دیگر انجام شود.
متغیر های Volatile
با اعلان یک متغیر بصورت volatile به کامپایلر میگوییم که از reordering جلوگیری کن. کامپایلر بدین صورت عمل میکند که اگر هر دو عملیات read و write برای یک متغیر ببیند ابتدا عملیات write و سپس read را انجام میدهد که منجر به جلوگیری از داده های Stale (کهنه ) میشود.
درواقع متغیر های volatile صرفا برای Visibility Problem استفاده میشوند.
محدودیت های متغیر های Volatile
متغیر های Volatile به اندازه ی کافی قوی نیستند که یک increment operation(count++ مثلا ) را بصورت Atomic پیاده سازی کنند مگر اینکه تضمین شود نوشتن متغیر صرفا توسط یک نخ انجام میپذیرد. راه حل : استفاده از Synchronized.
Synchronized میتواند هم visibility و هم atomicity را گارانتی کند درحالی که volatile صرفا visibility را گارانتی میکند.
چرا متغیر های Volatile در زمینه ی Increment operation ضعیف هستند؟
متاسفانه volatile صرفا تضمین میکند عملیات read قبل از write صورت بگیرد. مثلا اگر دو نخ همزمان اقدام به افزایش یک واحدی یک متغیر کنند ،بر خلاف انتظار متغیر ممکن است صرفا یک واحد افزایش پیدا کند. در چنین شرایطی نیاز به تضمین اتمیک بودن(Atomicity) عملیات خواهد بود.
چه زمانی از متغیر های volatile استفاده کنیم ؟
فرض کنید a=1 و دو نخ میخواهند a++ را اجرا کنند .اگر دقیقا همزمان a افزایش دهند هر دو در ابتدا مقدار a=1 را مشاهده و سپس a++ میکنند یعنی در اخر a=2:
ترد A مقدار a را میبند(که یک است)
ترد B مقدار a را میبند(که یک است)
ترد A مقدار a را با یک جمع میکند و حاصل را در a قرار میدهد(که دو میشود)
ترد B مقدار a را با یک جمع میکنند و حاصل را در a قرار میدهد(که دو میشود)
در شرایط بالا میتوانیم از کلاس های اماده جاوا مثل AtomicInteger که عملیات خواندن نوشتن و تغییر دادن متغیر را بصورت اتمیک ارائه میدهند استفاده کنیم(در مقاله ای جدا به اینکه چطور همچین عملیاتی را بصورت اتمیک ارائه میدهند خواهیم پرداخت).
برای اینکه بتوانیم شرایطی را فراهم اوریم که یک متغیر volatile بتواند increment شود(در حالت کلی بتواند به مقدار خودش وابسته باشد و بر مبنای ان تغییر یابد) بدون اینکه نخ ها را محدود کنیم، بایستی کاری کنیم که عملیات خواندن مقدار متغیر و افزایش ان بصورت Atomic صورت بگیرد. یعنی مثلا اگر نخ A مقدار a را یک دید، بصورت متوالی و Atomic افزایش هم انجام شود در این صورت ترد B مقدار 2 را میبیند.برای این منظور از الگوریتمی به نام Compare and Swap استفاده میکنیم که در کلاس های Atomic جاوا مثل AtomicInteger از همین الگوریتم استفاده میشود.
Compare and Swap
برای اینکه بتوانیم عملیات Read و Write را بصورت Atomic انجام دهیم از چنین الگوریتمی استفاده میکنیم. مثلا فرض کنید میخواهیم عملیات increment را بصورت Atomic پیاده سازی کنیم :
class EmulatedCAS { public synchronized int getValue() { public synchronized int compareAndSwap(int expectedValue, int newValue) { class Counter { public int getValue() { public int increment() { |
همانطور که مشاهده میکنید موقعی که میخواهیم مقدار متغیر را تغییر دهیم. عملیات خواندن و نوشتن درون بلاک synchronized انجام میپذیرد و این یعنی در زمان خواندن و قبل از تغییر دادن مقدار هیچ نخ دیگری نمیتواند بخواند پس مشکلات ذکر شده دیگر رخ نمیدهند.
منابع
بستن *نام و نام خانوادگی * پست الکترونیک * متن پیام |
دوره های آموزشی برنامه نویسی
انجام پروژه های برنامه نویسی
تدریس خصوصی برنامه نویسی
بیش از 7 سال از فعالیت جاواپرو میگذرد
جاواپرو دارای مجوز نشر دیجیتال از وزارت فرهنگ و ارشاد اسلامی است
جهت ارتباط مستقیم با جاواپرو در واتساپ و تلگرام :
09301904690
بستن دیگر باز نشو! |