Hacker News

درک Std:Shared_mutex از C++17

نظرات

2 min read Via www.cppstories.com

Mewayz Team

Editorial Team

Hacker News

درک std::shared_mutex از C++17

std::shared_mutex که در C++17 معرفی شده است، یک همگام سازی اولیه است که به چندین رشته اجازه می دهد همزمان قفل های مشترک (خواندن) را نگه دارند و در عین حال دسترسی انحصاری را برای عملیات نوشتن تضمین می کند. یکی از رایج‌ترین چالش‌های همزمانی در C++ مدرن را با ارائه یک روش استاندارد و تمیز به توسعه‌دهندگان برای پیاده‌سازی قفل خواننده-نویسنده بدون دسترسی به کتابخانه‌های شخص ثالث یا APIهای خاص پلتفرم، حل می‌کند.

std::shared_mutex دقیقاً چیست و چرا در C++17 اضافه شد؟

قبل از C++17، توسعه‌دهندگانی که به معناشناسی خواننده-نویسنده نیاز داشتند، باید به راه‌حل‌های خاص پلتفرم مانند pthread_rwlock_t در سیستم‌های POSIX یا SRWLOCK در ویندوز تکیه می‌کردند، یا از کتابخانه‌های شخص ثالث مانند Boost استفاده می‌کردند. کمیته استاندارد C++17 این شکاف را تشخیص داد و std::shared_mutex را در سربرگ معرفی کرد تا مستقیماً به آن رسیدگی کند.

ایده اصلی ساده است: در بسیاری از برنامه های دنیای واقعی، داده ها بسیار بیشتر از آنچه نوشته شده خوانده می شوند. یک std::mutex استاندارد، تمام دسترسی‌ها را سریال می‌کند - خواندنی‌ها شامل آن می‌شود - که گلوگاه‌های غیرضروری ایجاد می‌کند. std::shared_mutex این محدودیت را با تمایز بین دو حالت قفل برطرف می کند:

  • قفل مشترک (خوانده شده) — از طریق lock_shared() به دست آمده است. چندین رشته می توانند این را به طور همزمان نگه دارند و آن را برای خواندن همزمان ایده آل می کند.
  • قفل انحصاری (نوشتن) — بدست آمده از طریق lock()؛ فقط یک رشته می‌تواند این را در یک زمان نگه دارد، و هیچ قفل مشترک در زمانی که نگه داشته می‌شود مجاز نیست.
  • std::shared_lock — یک پوشش RAII که lock_shared() را در ساخت و unlock_shared() را در هنگام تخریب فراخوانی می‌کند و از نشت منابع جلوگیری می‌کند.
  • std::unique_lock / std::lock_guard — با حالت انحصاری استفاده می‌شود و اطمینان می‌دهد که عملیات نوشتن کاملاً محافظت شده و ایمن است.

این طراحی حالت دوگانه، std::shared_mutex را برای سناریوهایی مانند حافظه پنهان، رجیستری‌های پیکربندی، و هر ساختار داده‌ای که خواندن بر حجم کار غالب است، مناسب می‌کند.

چگونه از std::shared_mutex در کد واقعی با نظرات استفاده می کنید؟

نظرات در کدهایی که از std::shared_mutex استفاده می‌کنند بسیار ارزشمند هستند، زیرا منطق همزمانی بسیار دشوار است. نظرات درست نشان می دهد که چرا یک نوع قفل خاص انتخاب شده است، که به طور چشمگیری خطر معرفی تصادفی مسابقات داده توسط نگهبانان آینده را کاهش می دهد. در اینجا یک الگوی معمولی وجود دارد:

#include 
#include 
#include 

کلاس ConfigRegistry {
    mutable std::shared_mutex mtx_; // از نقشه زیر محافظت می کند
    std::unordered_map data_;

عمومی:
    // مسیر خواندن: چندین رشته ممکن است به طور همزمان این را فراخوانی کنند
    std::string get(const std::string& key) const {
        std::shared_lock lock(mtx_); // قفل اشتراکی - امن برای خواندن همزمان
        auto it = data_.find(key);
        آن را برگردانید != data_.end() ? it->second : "";
    }

    // مسیر نوشتن: دسترسی انحصاری مورد نیاز است
    مجموعه خالی (const std::string& key, const std::string& val) {
        std::unique_lock lock(mtx_); // قفل انحصاری - همه خوانندگان را مسدود می کند
        data_[key] = val;
    }
};

توجه کنید که نظرات چگونه قصد پشت هر انتخاب قفل را توضیح می‌دهند، نه اینکه صرفاً آنچه را که کد انجام می‌دهد مجدداً بیان کنید. این استاندارد طلایی است: نظرات باید به چرا پاسخ دهند، نه چی. کلمه کلیدی mutable در mutex به get() اجازه می‌دهد تا const را اعلام کند در حالی که همچنان می‌تواند قفل شود، یک الگوی رایج و اصطلاحی.

Insight کلید: همیشه از بسته‌بندی‌های قفل RAII (std::shared_lock، std::unique_lock) با std::shared_mutex استفاده کنید — هرگز به صورت دستی lock() () و را به صورت دستی صدا نکنید. قفل کردن دستی در حضور استثناها یک مسیر تضمین شده برای بن بست و رفتار تعریف نشده است.

مشکلات رایج هنگام کار با std::shared_mutex چیست؟

حتی با نظرات روشن و نیت خوب، std::shared_mutex تله های ظریفی دارد که توسعه دهندگان با تجربه را به دام می اندازد. خطرناک ترین آنها ارتقا قفل است: هیچ راهی داخلی برای ارتقاء قفل مشترک به یک قفل انحصاری بدون آزاد کردن قفل وجود ندارد. تلاش برای انجام این کار بدون رها کردن، یک بن‌بست فوری ایجاد می‌کند زیرا رشته در حالی که منتظر قفل انحصاری است، قفل مشترکی را نگه می‌دارد که تا زمانی که قفل مشترک وجود دارد - از جمله قفلی که نگه داشته است - هرگز نمی‌توان آن را اعطا کرد.

💡 DID YOU KNOW?

Mewayz replaces 8+ business tools in one platform

CRM · Invoicing · HR · Projects · Booking · eCommerce · POS · Analytics. Free forever plan available.

Start Free →

یک اشتباه رایج دیگر محافظت از دانه بندی اشتباه است. توسعه‌دهندگان گاهی اوقات خیلی گسترده قفل می‌کنند، هدف الگوی خواننده-نویسنده را نادیده می‌گیرند، یا خیلی باریک، پنجره‌هایی را رها می‌کنند که در آن متغیرها بین دو قفل جداگانه نقض می‌شوند. نظراتی که محافظت ناپذیر را توصیف می‌کنند، به جای اینکه متغیر فقط قفل شود، به تیم‌ها کمک می‌کند در طول بررسی کد درباره درستی آن استدلال کنند.

عملکرد نیز می تواند شما را شگفت زده کند. در سیستم‌های بسیار پرمشاهده با نویسنده‌های زیادی، std::shared_mutex ممکن است در واقع بدتر از یک std::mutex معمولی به دلیل هزینه‌های اضافی حسابداری عمل کند. همیشه قبل از اینکه فرض کنید قفل کردن خواننده-نویسنده یک برد خالص است، نمایه کنید.

std::shared_mutex چگونه با std::mutex و سایر گزینه ها مقایسه می شود؟

std::mutex ساده‌تر است، زمانی که اختلاف کم است سریع‌تر به دست می‌آید، و زمانی که خواندن و نوشتن با فرکانس تقریباً یکسانی اتفاق می‌افتد مناسب است. std::shared_mutex زمانی می درخشد که میزان خواندن به میزان قابل توجهی از تعداد نوشته ها بیشتر باشد - نسبت 10:1 یا بالاتر قبل از در نظر گرفتن سوئیچ یک قانون منطقی است.

C++14 std::shared_timed_mutex را معرفی کرد، که try_lock_shared_for() و try_lock_shared_until() را برای تلاش‌های زمان‌دار اضافه می‌کند. std::shared_mutex C++17، انواع زمان‌بندی‌شده را برای اجرای ناب‌تر حذف می‌کند. اگر به قفل کردن زمان‌بندی شده در مسیر مشترک نیاز دارید، std::shared_timed_mutex در دسترس باقی می‌ماند و هر دو نوع کاملاً استاندارد هستند.

برای جایگزین‌های بدون قفل، std::atomic همراه با نظم‌دهی دقیق حافظه گاهی اوقات می‌تواند به طور کامل جایگزین mutex برای پرچم‌ها یا شمارنده‌های ساده شود، اما برای ساختارهای داده پیچیده، std::shared_mutex خواناترین و قابل نگهداری‌ترین راه‌حل در کتابخانه استاندارد باقی می‌ماند.

سوالات متداول

آیا std::shared_mutex می تواند باعث گرسنگی شود؟

بله، می تواند. اگر دارندگان قفل مشترک جدید به طور مداوم وارد شوند، یک درخواست کننده قفل انحصاری ممکن است به طور نامحدود منتظر بماند - یک مشکل کلاسیک گرسنگی نویسنده. استاندارد C++ خط مشی انصاف خاصی را الزامی نمی کند، بنابراین رفتار بستگی به اجرا دارد. در عمل، اکثر پیاده‌سازی‌های استاندارد کتابخانه، قفل‌های انحصاری معلق را پس از قرار گرفتن در صف اولویت‌بندی می‌کنند، اما اگر گرسنگی یک نگرانی در تولید است، باید این را برای زنجیره ابزار و پلتفرم خاص خود تأیید کنید.

آیا استفاده از std::shared_mutex با std::condition_variable بی خطر است؟

std::condition_variable به std::unique_lock نیاز دارد، بنابراین مستقیماً با std::shared_mutex سازگار نیست. اگر لازم است در حین نگه‌داشتن یک mutex مشترک روی یک شرط منتظر بمانید، از std::condition_variable_any استفاده کنید، که با هر نوع BasicLockable از جمله std::shared_mutex جفت‌شده با std::shared_lock کار می‌کند.

آیا باید هر بار که از std::shared_mutex استفاده می‌کنم نظر اضافه کنم؟

حداقل، اعلان mutex را نظر دهید تا توضیح دهید از چه داده هایی محافظت می کند و متغیرهایی که حفظ می کند. در هر سایت قفل، یک نظر مختصر که توضیح می دهد چرا دسترسی اشتراکی در مقابل دسترسی انحصاری انتخاب شده است، ارزش قابل توجهی را برای بازبینان کد و نگهبانان آینده می افزاید. بازتولید و رفع اشکال‌های همزمانی از سخت‌ترین اشکال‌ها هستند، بنابراین سرمایه‌گذاری در نظرات واضح و دقیق سود چند برابری دارد.


مدیریت سیستم‌های پیچیده - چه کد C++ همزمان یا کل عملیات تجاری - به ابزارهای مناسب و ساختار واضح نیاز دارد. Mewayz سیستم‌عامل تجاری 207 ماژول است که بیش از 138000 کاربر به آن اعتماد دارند تا همین وضوح را در بازاریابی، CRM، تجارت الکترونیک، تجزیه‌وتحلیل و موارد دیگر به ارمغان بیاورد، همه در یک پلتفرم با شروع فقط 19 دلار در ماه. دست از دستکاری ده ها ابزار قطع شده بردارید و با دقت نرم افزاری که به خوبی طراحی شده است، کسب و کار خود را شروع کنید. امروز Mewayz را در app.mewayz.com امتحان کنید و ببینید چگونه یک سیستم یکپارچه روش کار تیم شما را تغییر می‌دهد.