امین پایدار
امین پایدار
خواندن ۳ دقیقه·۶ سال پیش

در رو پشت سرمون قفل کنیم یا پیغام بگذاریم ؟

برنامه‌‌هایی که در کد‌هایشان موازی‌سازی دارند مشکل مشترکی دارند. حالا بیاید ببینیم این مشکل چیه.

برنامه‌ی شما از یک بخش حافظه استفاده می‌کند. وقتی که برنامه‌ی شما از یک نخ استفاده کند هیچ مشکلی ندارید چرا که در هر زمان فقط یک نخ منتظر حافظه می‌شود؛

اما وقتی که چند نخ بخواهند از حافظه استفاده کنند ممکن است تغییرات هم‌دیگر در حافظه را دوباره‌نویسی کنند.

برای حل این مشکل سه روش پیش رو دارید:

  1. قفل‌ها
  2. مدل اکتور
  3. حافظه تراکنشی نرم‌افزار

در ادامه به توضیح هر یک از این راه‌حل ها می‌پردازیم.


قفل‌ها

با استفاده از قفل‌ها مانع اجرای یک تکه از کد توسط چند نخ به‌صورت هم‌زمان می‌شویم.

نخی که وارد بخش بحرانی می‌شود آن را قفل می‌کند و نخ دیگر نمی‌تواند وارد بخش بحرانی شود، زمانی که نخ اول کارش تمام شود قفل را باز می‌کند و نخ دوم وارد آن می‌شود.

مدل اکتور

طراح مدل اکتور آقای Carl Hewitt است که ایده‌ی مدل اکتور را از نظریه‌ی نسبیت عام و مکانیک کوانتومی گرفته‌است. در این مدل، هر اکتور حالت درونی خود را دارد و تنها را ارتباط با یک اکتور فرستادن پیام به آن است.

اکتور‌ها برای اینکه از حالت هم‌دیگر باخبر شوند و یا حالت درونی یکدیگر را تغییر دهند، با فرستادن پیام اینکار را انجام می‌دهند.

در مدل اکتور:

  • اکتور‌ها به هیچ‌وجه حالت درونی خود را به اشتراک نمی‌گذارد؛ به همین دلیل نیازی به دعوا سر قفل برای دسترسی به داده‌ی اشتراکی نیست.
  • اکتور‌ها بلاک نمی‌شوند به همین دلیل هیچ وقت بن‌بست نخواهیم داشت.
  • اکتورها بین نخ‌های مختلف به اشتراک گذاشته نمی‌شوند، پس همیشه یک نخ به حالت درونی اکتور دسترسی دارد.
  • وقتی که پیامی به یک اکتور فرستاده می‌شود وارد جعبه‌ی پیام‌هایش می‌رود و اکتور یک‌به‌یک و به ترتیب عمل مورد نظر را انجام می‌دهد.


حافظه تراکنشی نرم‌افزار (Software Transactional Memory)

استفاده از STM بسیار ساده‌است و جایگزینی برای قفل‌ها می‌باشد. ایده‌ی STM شبیه تراکنش‌های پایگاه‌داده است.

در زبان‌های مختلف پیاده‌سازی های مختلفی از STM وجود دارد که در این لینک لیستی از آنها وجود دارد. نحوه‌ی کار به این شکل است:

متغیر‌ها را از جنس TVar تعریف می‌کنیم. برای به‌روز رسانی مقدار متغیر از writeTVar و برای خواندش از readTVar استفاده می‌کنیم.

  1. وقتی که TVar روی یک نخی ایجاد می‌شود، یک لاگ تراکنش برای آن نخ ایجاد می‌شود.

۲. وقتی writeTVar با مقداری جدید فراخوانی می‌شود، آدرس TVar و مقدار جدیدش در لاگ تراکنش نوشته می‌شود.

۳. وقتی readTVar فراخوانی می‌شود، ابتدا سراغ لاگ تراکنش می‌رود تا آخرین مقداری که با استفاده از writeTVar برای نوشتن در متغیر به لاگ اضافه شده‌است را بیابد، درصورتی که چیزی پیدا نکند سراغ خود TVar رفته و مقدار خود آن را برداشته سپس آدرس TVar و مقدارش در لاگ اضافه می‌شود.

وقتی اجرای بلاک‌های اتمیک توسط هر نخ تمام شود باید لاگ‌ها معتبر شوند. این معتبرسازی به‌صورت زیر انجام می‌شود:

هر‌یک از readTVar های موجود در لاگ‌ها را چک می‌کنیم که مقدار برگردانده‌شده با مقدار واقعی درون TVar یکسان باشد اگر یکسان باشد معتبرسازی با موفقیت تمام می‌شود و مقدار جدید از لاگ تراکنش به درون TVar ریخته می‌شود.

اگر معتبر سازی با موفقیت انجام نشود، لاگ تراکنش حذف شده و بلاک اتمیک دوباره از ابتدا اجرا می‌شود.



نتیجه‌گیری

قفلها

  • در اکثر زبان‌ها وجود دارد
  • به شما ابزار کنترل دقیق رو کدتان را می‌دهد
  • استفاده از آن سخت است
  • ممکن است کدتان دچار بن‌بست یا قحطی(starvation) شود

مدل اکتور

  • حالت اشتراکی وجود ندارد، درنتیجه thread-safe بودن مثل آب خوردن است
  • قفلی وجود ندارد، در‌نتیجه بن‌بستی وجود ندارد
  • تمام آنچه که کد شما نیاز دارد اکتور و فرستادن پیام است، درنتیجه احتمالا نیاز به تغییر معماری کدتان داشته باشید

حافظه تراکنشی نرم‌افزار

  • استفاده از آن ساده است. اگر در حال حاضر از قفل‌ها استفاده کرده اید مهاجرت به STM ساده است
  • قفلی وجود ندارد، در‌نتیجه بن‌بستی وجود ندارد



برگرفته از پست زیر

http://adit.io/posts/2013-05-15-Locks,-Actors,-And-STM-In-Pictures.html



برنامه‌نویسینخنخ ایمنموازی‌سازیهمگامی
Software Engineer at BaleMessenger
شاید از این پست‌ها خوشتان بیاید