همروندی توسعهپذیر با 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>
، فراهم میکند که استفاده از آنها در زمینههای همزمان ایمن است. سیستم نوعی
و کنترلکننده وامدهی تضمین میکنند که کدی که از این راهحلها استفاده میکند با رقابتهای
داده یا ارجاعهای نامعتبر مواجه نمیشود. هنگامی که کد شما کامپایل شود، میتوانید مطمئن
باشید که بدون آن دسته از اشکالهای سختردیابی که در زبانهای دیگر معمول است، به خوبی
روی چندین نخ اجرا خواهد شد. برنامهنویسی همزمان دیگر مفهومی برای ترسیدن نیست:
پیش بروید و برنامههای خود را بیباکانه همزمان کنید!