خوشبختانه هر توسعه دهنده ای می داند که اجازه دسترسی همزمان به متغیرها کار درستی نیست . اعمال این دسترسی باید به صورت زمانبندی شده (synchronized) انجام شود تا از ایجاد مشکل و پیچیدگی در دیباگ کردن کد جلوگیری شود. تقریبا همه تصویری واضح دارند که متغیر چیست ، اما سردرگمی و سوء تفاهم زیادی درمورد معنای همزمانی در اجرا در این زمینه وجود دارد.
به عنوان مثال ، با استفاده از coroutines(مجوز) های کاتلین ، قطعه کد زیر را در نظر بگیرید:
val m = mutableMapOf<Int,String>()
m[1] = "one" // (1)
yield() // or other suspending function
m[2] = "two" // (2)
ما یک متغیر m داریم که دو بار تغییر کرده و تابع yield() را که موجب ایجاد وقفه میشود در بین این تغییر ها فراخوانی کردیم . مجوز اجرای این کد پس از خط (1) به حالت تعلیق در می آید و ممکن است در thread دیگری برای اجرای خط (2) از سر گرفته شود. ما به هیچ وجه برای اعمال تغییرات بر روی متغیر m زمان بندی انجام ندادیم ، در واقع متغیر m همزمان در دو thread مجزا به صورت اشتراکی استفاده میشود. آیا این باعث ایجاد مشکل میشود؟
در جواب باید بگویم: خیر. در واقع ، پیاده سازی تابع yield() به منزله یک ایجاد کننده وقفه, کار همزمان سازی(synchronization) را برای ما انجام میدهد تا اطمینان حاصل شود که کد قبل از yield() دقیقا قبل از تابع اجرا میشود و در نتیجه با اجرای کد بعد از تابع همزمانی ندارد.
پس تعریف دسترسی همزمان چیست؟ این کلمه تعریف دقیقی دارد .دو عملیات همزمان هستند اگر ترتیب اجرایی برای انها در نظر گرفته نشده باشد. وقتی کدها را روی دو thread مختلف اجرا می کنیم مهم نیست که زمان واقعی بین دو عملیات چقدر می گذرد. میزان همزمانی از نظر زمان خطی تعریف نشده است. ما می توانیم عملیات (1) و (2) را داشته باشیم که ده ثانیه از هم جدا شده بر روی thread های مختلف انجام می شود و آنها هنوز هم همزمان هستند و باعث ایجاد همه مشکلات می شوند ، مگر اینکه از قبل ترتیب اجرا بین آنها را تعیین کرده باشیم.
امااین همزمانی کجا اتفاق می افتد؟ خوب ، وقتی ما یک کد تک رشته ای را می نویسیم ، تمام کدها در یک thread به ترتیب خطی اجرا میشود ، بنابراین کلیه کدها در یک thread مشخص به صورت خط به خط اجرا می شوند ، دروقع پشت سر هم. پس در یک thread متغیر بدون دسترسی سایر منابع به صورت انحصاری تغییر میکند.
در هر حال ، هنگامی که ما thread های مختلفی داریم ، همه عملیات ها در threadهای مختلف همزمان هستند ، مگر اینکه از عملیات همگام سازی استفاده شود. عملیات همگام سازی قبل از ارتباط بین عملیات در thread های مختلف اتفاق می افتد و به ما امکان می دهد mutable state را به صورت متوالی تغییر دهیم .
نکته ای که در مورد coroutine ها باید در نظر داشته باشید این است که اگرچه یک coroutine در کاتلین می تواند به صورت چند نخی اجرا شود ، دقیقاً مانند نخ از نقطه نظر mutable state است. هیچ دو عمل در یک coroutine یکسان نمی تواند همزمان باشد. و درست مانند threadها ، باید از به اشتراک گذاشتن mutable state های خود در بین coroutine ها خودداری کنید!
از به اشتراک گذاشتن mutable state ها در کدنویسی خودداری کنید. هر mutable state را به یک thread واحد یا یک coroutine اختصاص دهید.