همروندی توسعهپذیر با traitهای Send و Sync
جالب است که تقریباً تمام ویژگیهای همروندی که تا اینجای فصل دربارهشان صحبت کردیم، بخشی از کتابخانهی استاندارد بودهاند، نه زبان. گزینههای شما برای مدیریت همروندی محدود به زبان یا کتابخانهی استاندارد نیست؛ میتوانید ویژگیهای همروندی خود را بنویسید یا از آنهایی استفاده کنید که دیگران نوشتهاند.
با این حال، در میان مفاهیم کلیدی همروندی که در خود زبان (و نه در کتابخانهی استاندارد) گنجانده شدهاند، traitهای std::marker یعنی Send و Sync قرار دارند.
اجازه انتقال مالکیت بین نخها با Send
trait نشانهگذاریشدهی Send مشخص میکند که مالکیت مقادیر نوعی که این trait را پیادهسازی کرده، میتواند بین نخها منتقل شود. تقریباً همهی نوعهای Rust، Send را پیادهسازی میکنند، اما برخی استثناها نیز وجود دارند؛ از جمله Rc<T>: این نوع نمیتواند Send را پیادهسازی کند، چرا که اگر یک مقدار Rc<T> را clone کنید و بخواهید مالکیت آن را به نخ دیگری منتقل کنید، ممکن است هر دو نخ همزمان شمارندهی رفرنس را بهروزرسانی کنند. به همین دلیل، Rc<T> برای استفاده در موقعیتهای تکنخی طراحی شده است، جایی که نمیخواهید هزینهی عملکردی مرتبط با ایمنی نخ را بپردازید.
بنابراین، سیستم نوع Rust و محدودیتهای trait تضمین میکنند که هرگز بهطور ناخواسته نتوانید یک مقدار Rc<T> را بهشکل ناایمن بین نخها منتقل کنید. زمانی که سعی کردیم این کار را در لیستینگ 16-14 انجام دهیم، خطایی دریافت کردیم با این مضمون که trait `Send` برای `Rc<Mutex<i32>>` پیادهسازی نشده است. اما زمانی که به Arc<T> تغییر دادیم، که Send را پیادهسازی میکند، کد با موفقیت کامپایل شد.
هر نوعی که بهطور کامل از نوعهای Send تشکیل شده باشد بهطور خودکار به عنوان
Send علامتگذاری میشود. تقریباً تمام نوعهای اولیه Send هستند، به جز
اشارهگر (Pointer)های خام، که در فصل 20 درباره آنها صحبت خواهیم کرد.
اجازه دسترسی از چندین نخ با Sync
trait نشانهگذاریشدهی Sync مشخص میکند که ارجاع دادن به نوعی که این trait را پیادهسازی کرده از چندین نخ بهصورت همزمان بیخطر است. بهعبارت دیگر، هر نوعی T زمانی Sync را پیادهسازی میکند که &T (یک رفرنس غیرقابل تغییر به T) Send را پیادهسازی کرده باشد، یعنی این رفرنس میتواند با اطمینان به نخ دیگری ارسال شود. مشابه Send، تمام نوعهای اولیه (primitive types) Sync را پیادهسازی میکنند، و نوعهایی که بهطور کامل از نوعهایی تشکیل شدهاند که خود Sync هستند، نیز بهطور خودکار Sync را پیادهسازی میکنند.
اشارهگر هوشمند Rc<T> نیز برای همان دلایلی که Send را پیادهسازی نمیکند، Sync را نیز پیادهسازی نمیکند. نوع RefCell<T> (که در فصل ۱۵ دربارهی آن صحبت کردیم) و خانوادهی نوعهای مرتبط با Cell<T> نیز Sync را پیادهسازی نمیکنند. پیادهسازی بررسی وامگیری (borrow checking) که RefCell<T> در زمان اجرا انجام میدهد، برای نخهای مختلف ایمن نیست. اشارهگر هوشمند Mutex<T> Sync را پیادهسازی میکند و میتواند برای اشتراکگذاری دسترسی بین چندین نخ استفاده شود، همانطور که در [«اشتراکگذاری یک Mutex<T> بین چند نخ»][sharing-a-mutext-between-multiple-threads] مشاهده کردید.
پیادهسازی دستی Send و Sync ناایمن است
از آنجا که نوعهایی که بهطور کامل از نوعهای دیگری تشکیل شدهاند که خودشان Send و Sync را پیادهسازی کردهاند، بهصورت خودکار این دو trait را پیادهسازی میکنند، نیازی به پیادهسازی دستی آنها نداریم. بهعنوان marker traitها، این traitها حتی هیچ متدی برای پیادهسازی ندارند. آنها فقط برای اعمال کردن محدودیتهایی مرتبط با همزمانی مفید هستند.
پیادهسازی دستی این ویژگیها شامل پیادهسازی کد ناایمن در راست میشود. ما در فصل
20 درباره استفاده از کد ناایمن در راست صحبت خواهیم کرد؛ فعلاً، اطلاعات مهم این
است که ساخت نوعهای همزمان جدید که از قسمتهای Send و Sync تشکیل نشدهاند
نیاز به دقت زیادی دارد تا اصول ایمنی رعایت شوند. “The Rustonomicon”
اطلاعات بیشتری درباره این اصول و نحوه رعایت آنها ارائه میدهد.
خلاصه
این آخرین باری نیست که در این کتاب با همزمانی (concurrency) روبهرو میشوید: فصل بعدی بر برنامهنویسی async تمرکز دارد و پروژهی فصل ۲۱ مفاهیم این فصل را در یک موقعیت واقعیتر نسبت به مثالهای کوچکی که در اینجا بررسی شد بهکار خواهد گرفت.
همانطور که قبلاً اشاره شد، به دلیل اینکه بخش بسیار کمی از نحوه مدیریت همزمانی در راست بخشی از زبان است، بسیاری از راهحلهای همزمانی بهعنوان crate پیادهسازی شدهاند. اینها سریعتر از کتابخانه استاندارد تکامل مییابند، بنابراین حتماً به صورت آنلاین جستجو کنید تا crateهای بهروز و پیشرفتهای که برای موقعیتهای چندریسمانی مناسب هستند را پیدا کنید.
کتابخانه استاندارد راست کانالهایی برای ارسال پیام و انواع اسمارت پوینتر، مانند Mutex<T>
و Arc<T>، فراهم میکند که استفاده از آنها در زمینههای همزمان ایمن است. سیستم نوعی
و کنترلکننده وامدهی تضمین میکنند که کدی که از این راهحلها استفاده میکند با رقابتهای
داده یا ارجاعهای نامعتبر مواجه نمیشود. هنگامی که کد شما کامپایل شود، میتوانید مطمئن
باشید که بدون آن دسته از اشکالهای سختردیابی که در زبانهای دیگر معمول است، به خوبی
روی چندین نخ اجرا خواهد شد. برنامهنویسی همزمان دیگر مفهومی برای ترسیدن نیست:
پیش بروید و برنامههای خود را بیباکانه همزمان کنید!