Saturday, May 12, 2007

volatile

در میان کلمات کلیدی زبان جاوا دو کلمه کلیدی volatile و transient خیلی مظلوم واقع شده اند و کمتر کسی هست که به آن ها بپردازد. دلیل آن هم این هست که اولا به اشتباه احساس می شود که خیلی کم به این کلمات نیاز پیدا می کنیم، ثانیا توضیح اینکه این کلمات چه کاری انجام می دهند کمی مشکل هست. در هر حال بدون هیچ دلیل خاصی تصمیم گرفتم امروز در مورد کلمه کلیدی volatile در زبان جاوا بنویسم. استفاده از کلمه کلیدی volatile در موارد زیادی ضرورت دارد. یکی از جاهایی که به این کلمه کلیدی نیاز داریم برنامه نویسی بر روی کامپیوترهای چند پردازنده است. البته استفاده نکردن از این کلمه کلیدی هنگام برنامه نویسی Multithreaded بر روی ماشینی با یک پردازنده هم می تواند ایجاد اشکال کند ولی در اینجا نمی خواهم به این موضوع بپردازم. برای اینکه کاربرد این کلمه کلیدی را درک کنیم باید تا حدودی با معماری کامپیوترهای چندپردازنده آشنا باشیم.

چندپردازنده ها عموما بسیار متنوع و پیچیده هستند، ولی گونه خاصی از آنها را که در اینجا می خواهم به آن بپردازم چندپردازنده های متقارن یا همان SMP ها هستند. در معماری SMP معمولا چند پردازنده هم شکل(متقارن) به یک حافظه مشترک دسترسی دارند. همان طور که می دانید معمولا در کامپیوترها برای تسریع دسترسی به حافظه از حافظه پنهان (یا همان Cache) استفاده می شود. استفاده از حافظه پنهان در SMP ها حداقل می تواند به دو شکل مختلف باشد:

1. تمام پردازنده ها از یک حافظه پنهان مشترک استفاده کنند، در این حالت هنگامی که یک پردازنده مقدار یک متغیر را تغییر می دهد و بلافاصله پردازنده دیگر مقدار آن را می خواند، مقدار خوانده شده با مقدار نوشته شده یکسان است.

2. هر یک از پردازنده ها حافظه پنهان خودشان را داشته باشند، در این حالت فرض کنید قرار است دو پردازنده مختلف به متغیر مشترک X دسترسی داشته باشند و این متغیر در حافظه پنهان هر دو پردازنده قرار دارد(دو مکان متفاوت). حال فرض کنید پردازنده اول مقدار X را در حافظه پنهان خود تغییر دهد و پردازنده دوم آن را از حافظه پنهان خود بخواند. از آنجایی که خواندن و نوشتن در دو مکان مختلف انجام شده ممکن است این مقدار خوانده شده با مقدار نوشته شده متفاوت باشد.

استفاده از حافظه پنهان مشترک در کامپیوترهایی که تعداد زیادی پردازنده دارند امکان پذیر نیست، چرا که در اینصورت به دلیل تعداد زیاد مراجعات به حافظه پنهان، خود حافظه پنهان به گلوگاه کارایی تبدیل شده و عملا خاصیت خود را از دست می دهد. از این رو اغلب در معماری SMP ها از شکل دوم و یا ترکیبی از شکل اول و دوم استفاده می شود.

برای حل مشکل در حالتی پردازنده ها حافظه پنهان مشترک ندارند، دو راهکار کلی وجود دارد. اول اینکه هنگامی که لازم است مقداری در حافظه پنهان یک پردازنده نوشته شود، این مقدار در حافظه پنهان دیگر پردازنده ها نیز نوشته شود. دوم اینکه پردازنده از دستورالعمل هایی پشتیبانی کند که در مواقع لازم بتوان مقدار موجود در حافظه پنهان را بطور مستقیم در حافظه اصلی نوشت و یا مقداری را بطور مستقیم و بدون توجه به مقدار موجود در حافظه پنهان، از حافظه اصلی خواند(دستور العمل های مدیریت حافظه پنهان).

در حالت آخر برنامه نویس باید برای کامپایلر مشخص کند که چه متغیرهایی به صورت مشترک بین Thread های برنامه استفاده می شود تا کامپایلر بتواند کدهای لازم برای مدیریت حافظه پنهان را تولید کند. توجه کنید که اگر قرار باشد این کار بر روی تمام متغیرهای مشترک بین Thread ها انجام شود، کارایی چند پردازنده به شدت کاهش می یابد. از این رو لازم است کاربر متغیرهایی را که لازم می داند بطور صریح مشخص کند.

فیلدهایی از کلاس که با کلمه کلیدی volatile تعریف شده اند، برای ارتباط بین Threadهای مختلف استفاده می شوند. به عبارت دقیق تر هر بار خواندن از یک فیلد volatile آخرین مقدار نوشته شده بر روی آن فیلد (توسط هر Thread و بر روی هر پردازنده ای) را بر می گرداند.

گرچه نحوه پیاده سازی این موضوع در معماری های مختلف، متفاوت است ولی بر اساس تعریفی که در JSR 133 از مدل حافظه جاوا آمده، کامپایلر و محیط زمان اجرای جاوا موظف هستند، شرایط را طوری فراهم کنند که هنگام خواندن یک فیلد volatile همواره آخرین مقداری که درون آن نوشته شده برگردانده شود.

البته دقت کنید که بجای استفاده از volatile در بسیاری از موارد می توانید از کلمه کلیدی synchronized هم استفاده کنید. همچنین دوباره بر این نکته تاکید می کنم که مشکلی که در اینجا مطرح شد حتی در ماشین های تک پردازنده هم ممکن است اتفاق بیفتد(اینجا را ببینید).

در هر حال نوشتن برنامه های Multithreaded توجه و مطالعه زیادی را می طلبد. مشکل اصلی در این برنامه ها این است که اشکالات موجود تنها در بعضی موارد خاص خود را نشان می دهد، به همین دلیل رفع اشکال آنها به سختی انجام می شود. آشنایی کامل بر مفاهیم Multithreading و تعاریف زبانی که با آن برنامه می نویسید می تواند باعث کاهش این مشکلات شود.