پیش از C++17، الگوریتم‌های کتابخانه‌ی استاندارد تنها به‌صورت ترتیبی اجرا می‌شدند. اما با معرفی سیاست‌های اجرایی (Execution Policies)، برنامه‌نویسان این امکان را یافتند تا با تغییر یک پارامتر، نحوه‌ی اجرای الگوریتم‌ها را کنترل کنند. این سیاست‌ها که در هدر <execution> و فضای نام std::execution تعریف شده‌اند، به کتابخانه می‌گویند که آیا یک الگوریتم می‌تواند به‌صورت موازی اجرا شود یا خیر . سه سیاست اصلی در C++17 وجود دارند: seq، par و par_unseq که هرکدام سطح مشخصی از موازی‌سازی را مجاز می‌دانند .

seq: اجرای ترتیبی و قابل‌پیش‌بینی

سیاست std::execution::seq یا ترتیبی، از کتابخانه می‌خواهد تا الگوریتم را به‌صورت دنباله‌ای و در همان ریسه‌ی فراخواننده اجرا کند . این خط‌مشی معادل اجرای نسخه‌های قدیمی‌تر و غیرموازی الگوریتم‌هاست و هیچ موازی‌سازی انجام نمی‌دهد . فراخوانی‌های توابع روی عناصر، به‌صورت نامعین دنباله‌دار (Indeterminately Sequenced) در یک ریسه اجرا می‌شوند، به این معنا که ترتیب نسبی آنها مشخص نیست اما روی همدیگر تأثیر نمی‌گذارند . استفاده از seq برای مجموعه‌داده‌های کوچک یا عملیات‌هایی که سربار موازی‌سازی از مزایای آن بیشتر است، انتخاب مناسبی محسوب می‌شود و همچنین تضمین می‌کند که هیچ رخداد رقابتی یا بن‌بستی به‌دلیل اجرای همزمان رخ نخواهد داد .

par: اجرای موازی با چندین ریسه

سیاست std::execution::par یا موازی، به کتابخانه اجازه می‌دهد تا الگوریتم را با استفاده از چندین ریسه به‌صورت همزمان اجرا کند . در این حالت، فراخوانی‌های توابع روی عناصر مختلف می‌توانند در ریسه‌های مختلفی اجرا شوند و حتی ممکن است در همان ریسه‌ی فراخواننده نیز اجرا گردند . تفاوت مهم par با seq در این است که ضمانت پیشرفت موازی (Parallel Forward Progress) را فراهم می‌کند، به این معنا که اگر یک ریسه یک قدم بردارد، در نهایت قدم دیگری نیز خواهد برداشت و این ویژگی به ریسه‌ها اجازه می‌دهد تا وارد بخش‌های بحرانی (Critical Sections) شده و قفل‌ها را به‌درستی مدیریت کنند . با این حال، مسئولیت جلوگیری از رخدادهای رقابتی و بن‌بست‌ها بر عهده‌ی برنامه‌نویس است و استفاده از این سیاست بدون همگام‌سازی مناسب می‌تواند به خرابی برنامه منجر شود .

par_unseq: ترکیبی از موازی‌سازی و بردارگونه‌سازی

سیاست std::execution::par_unseq یا موازی و بردارگونه، بالاترین سطح از موازی‌سازی را ممکن می‌سازد. این خط‌مشی علاوه بر اجازه‌ی اجرای موازی با چندین ریسه، به کتابخانه اجازه می‌دهد تا از دستورات بردارگونه (Vectorized) یا SIMD نیز برای افزایش سرعت استفاده کند . فراخوانی‌های توابع روی عناصر می‌توانند به‌صورت کاملاً نامرتب در ریسه‌های نامشخصی اجرا شوند و درون هر ریسه نیز نسبت به یکدیگر غیردنباله‌دار (Unsequenced) هستند، به این معنا که حتی می‌توانند با یکدیگر در هم آمیخته شوند . این ویژگی باعث می‌شود که استفاده از par_unseq محدودیت‌های مهمی داشته باشد: توابع فراخوانی‌شده نباید شامل عملیات‌های غیرایمن برای بردارگونه‌سازی باشند، مانند تخصیص حافظه، استفاده از std::atomic و سایر ابزارهای همگام‌سازی . همچنین ضمانت پیشرفت ارائه‌شده در این حالت ضعیف‌تر از par است و به ریسه‌ها اجازه‌ی ورود به بخش‌های بحرانی را نمی‌دهد، زیرا ممکن است ریسه‌ای که قفل را در دست دارد، دوباره زمان پردازنده دریافت نکند .

مسئولیت‌های برنامه‌نویس و نکات کلیدی

استفاده از سیاست‌های par و par_unseq مسئولیت‌های سنگینی بر عهده‌ی برنامه‌نویس می‌گذارد. جلوگیری از رخدادهای رقابتی و بن‌بست‌ها کاملاً بر عهده‌ی برنامه‌نویس است و کتابخانه هیچ تضمینی در این زمینه ارائه نمی‌دهد . همچنین در صورت پرتاب شدن یک استثناء از درون تابع فراخوانی‌شده در الگوریتم، std::terminate فراخوانی خواهد شد . انتخاب سیاست مناسب به عوامل مختلفی بستگی دارد: seq برای حالت‌های ساده و زمانی که موازی‌سازی سربار دارد، par برای محاسبات سنگین که نیاز به قفل‌گذاری دارند، و par_unseq برای عملیات‌های کاملاً مستقل و عددی که می‌توانند از بردارگونه‌سازی بهره ببرند . همچنین توجه داشته باشید که برخی از کامپایلرها مانند MSVC، تعداد ریسه‌های موازی را به تعداد هسته‌های منطقی سیستم محدود می‌کنند .


کلیدواژه ها : سیاست اجرایی C++17-std::execution::seq-std::execution::par-std::execution::par_unseq-الگوریتم‌های موازی C++17-هدر execution در C++-موازی‌سازی در STL-بردارگونه‌سازی C++17-مقایسه seq par par_unseq-مدیریت ریسه در C++17-الگوریتم‌های STL موازی-ضمانت پیشرفت موازی-پرفورمنس الگوریتم‌های موازی-نکات std::execution-آموزش سیاست‌های اجرایی C++17