6.4 KiB
1 - What are atomic variables? متغیرهای اتمیک متغیرهایی هستن که عملیات روشون به صورت یکپارچه و تو یه مرحله انجام میشه. یعنی نمیشه وسط کار یه ترد روی این متغیر، یه ترد دیگه بیاد دخالت کنه و کار رو نصفه بذاره.
فرق اصلیشون با متغیرهای معمولی اینه که مثلا تو یه متغیر عادی یه جمع ساده مثل i++ خودش شامل سه مرحله است: خوندن مقدار فعلی، جمع کردنش با یک، و نوشتن مقدار جدید. اگه دو تا ترد همزمان این کار رو بکنن با هم تداخل پیدا میکنن و دیتامون خراب میشه. اما متغیرهای اتمیک با کمک دستورات سطح سختافزار این کار رو بدون نیاز به قفل کردن و کاملا امن انجام میدن.
2 - Classes from java.util.concurrent.atomic چهار تا از کلاسهای معروف این پکیج این موارد هستن:
AtomicInteger
AtomicLong
AtomicBoolean
AtomicReference
مثلا در مورد AtomicInteger، بیشترین کاربردش برای ساختن شمارندههای امن هست. فرض کنید میخوایم تعداد بازدیدهای یه سایت رو بشمریم، چون ممکنه همزمان چند تا ترد بخوان این عدد رو ببرن بالا، با استفاده از این کلاس خیالمون راحته که با هم تداخل نمیکنن و هیچ عددی جا نمیفته.
3 - Compare locks with atomic variables اتمیکها کلا قفلی ندارن و با الگوریتمهای سختافزاری کار میکنن که باعث میشه تردها الکی متوقف نشن و خیلی سریع کارشون رو بکنن. اما قفلها بدبینانهتر عمل میکنن، یعنی وقتی یه ترد قفلی رو میگیره، بقیه تردها باید تو صف منتظر بمونن تا کارش تموم بشه.
اگه فقط با یه متغیر کار داریم قطعا استفاده از اتمیک بهتره چون سربار بلاک شدن تردها رو نداره و خیلی سریعتره. اما وقتی عملیات ما پیچیدهتره و روی چند تا متغیر مختلف تاثیر میذاره (مثل همین پروژه که پول از یه حساب کم میشه و به یه حساب دیگه اضافه میشه)، اینجا دیگه نمیشه فقط به اتمیک اعتماد کرد و حتما باید از قفل استفاده کنیم تا کل عملیات با هم انجام بشه.
4 - High contention and poor performance without race conditions این اتفاق وقتی میفته که ما از نظر ایمنی کدمون رو کامل ضد ضربه کردیم ولی انقدر همه جا رو قفل کردیم که تردها به جای اینکه موازی کار کنن، تو صف میمونن تا نوبتشون بشه. چند تا دلیل داره که باعث میشه برنامه کند بشه:
اولیش قفل کردن اضافیه. مثلا به جای اینکه فقط همون متغیر یا حساب خاص رو قفل کنیم، کل متد یا یه بخش بزرگی از برنامه رو قفل میکنیم. دلیل دوم رقابت شدید سر قفله. وقتی همه تردها بخوان مدام به یه منبع مشترک دسترسی پیدا کنن، سیستم زمان زیادی رو هدر میده تا فقط این صف و انتظارهای تردها رو مدیریت کنه. دلیل سوم هم گرسنگی تردهاست. یعنی یه سری ترد ممکنه هی عقب بیفتن و نتونن قفل رو بگیرن چون تردهای دیگه سریعتر قفل رو تصاحب میکنن، این باعث میشه کلا سرعت پردازش بیاد پایین.
5 - Why adding more threads does not always improve performance اضافه کردن ترد همیشه باعث بهبود سرعت نمیشه چون سختافزار ما ظرفیت محدودی داره. وقتی تعداد تردها خیلی زیاد بشه، سیستم عامل مجبوره مدام بینشون سوییچ کنه که خودش به شدت زمانبره.
از طرفی رقابت بالا میره و تردهای بیشتری تو حالت بلاک شده قرار میگیرن. یه مشکل دیگه هم کش پردازندههاست؛ وقتی هستههای مختلف میخوان یه متغیر مشترک رو تغییر بدن، دائم باید کش همدیگه رو باطل کنن که این کار پهنای باند مموری رو اشغال میکنه. علاوه بر این، خود مکانیزم قفل کردن و بیدار کردن تردها هم پردازنده رو درگیر میکنه و باعث افت بازدهی میشه.
6 - Why deadlocks appear in production and how to expose them بنبستها خیلی به زمانبندی دقیق تردها ربط دارن. ما وقتی داریم تست میکنیم، بار روی سیستم کمه و کارها راحت انجام میشن. ولی تو محیط واقعی به خاطر ترافیک و درخواستهای زیاد، سیستم عامل مجبوره تردها رو با ترتیبهای غیرقابل پیشبینی متوقف و اجرا کنه. تو این شلوغی احتمال اینکه دو تا ترد دقیقا تو یه لحظه متقاطع منابع هم رو قفل کنن و گیر بیفتن خیلی زیاده.
برای اینکه تو زمان تست بتونیم این باگها رو پیدا کنیم میشه دو تا کار کرد: اول اینکه بیایم استرس تست با همروندی بالا بنویسیم. یعنی مثلا صدها ترد رو همزمان بندازیم به جون دو سه تا حساب بانکی تا تراکنشهای ضربدری انجام بدن و شلوغی سیستم واقعی رو شبیهسازی کنیم. راه دوم اینه که عمدا تو بخشهای حساس کد یه وقفه چند میلیثانیهای بندازیم تا سیستم عامل مجبور بشه ترد رو متوقف کنه. اینطوری اگه مشکلی تو ترتیب قفلگذاری باشه زودتر خودش رو نشون میده.