باختصار:
- تم تنفيذ الدليل بالفعل في C ++ و JS و PHP ، وهو مناسب لـ Java .
- أسرع من Coroutine و Promise ، المزيد من الميزات.
- لا يتطلب مجموعة برامج منفصلة.
- Befriends جميع أدوات الأمان وتصحيح الأخطاء.
- يعمل على أي بنية ولا يتطلب أعلام مترجم خاصة.
انظر للخلف
في فجر الكمبيوتر ، كان هناك تدفق تحكم واحد مع حظر الإدخال والإخراج. ثم أضيفت انقطاعات حديدية إليه. الآن يمكنك استخدام الأجهزة البطيئة وغير المتوقعة بشكل فعال.
مع نمو قدرات الحديد وتوافرها المنخفض ، أصبح من الضروري إجراء العديد من المهام في وقت واحد ، مما وفر دعمًا للأجهزة. لذلك كانت هناك عمليات معزولة مع انقطاعات مستخرجة من الحديد في شكل إشارات.
كانت المرحلة التطورية التالية عبارة عن مؤشرات متعددة ، تم تنفيذها على أساس نفس العمليات ، ولكن مع الوصول المشترك إلى الذاكرة والموارد الأخرى. هذا النهج له حدوده ونفقات عامة كبيرة للتبديل إلى نظام تشغيل آمن.
للتواصل بين العمليات وحتى الآلات المختلفة ، تم اقتراح تجريد Promise / Future قبل أكثر من 40 عامًا.
أدت واجهات المستخدم ومشكلة العميل 10K المضحكة حاليًا إلى ذروة نهج Loop Event و Reactor و Proactor ، والتي تكون أكثر توجهاً نحو الحدث من منطق أعمال واضح ومتسق.
أخيرًا ، وصلنا إلى coroutine الحديث (coroutine) ، والذي هو في جوهره محاكاة للتدفقات على قمة التجريد الموصوف أعلاه مع القيود التقنية المقابلة والانتقال الحتمية للتحكم.
لنقل الأحداث والنتائج والاستثناءات ، عادوا جميعًا إلى نفس مفهوم الوعد / المستقبل. قررت بعض المكاتب أن تسمي بشكل مختلف قليلاً - "المهمة".
في النهاية ، أخفوا كل شيء في حزمة جميلة غير async/await
، الأمر الذي يتطلب دعمًا مترجمًا أو مترجمًا اعتمادًا على التكنولوجيا.
مشاكل المواقف المنطقية التجارية غير المتزامنة الحالية
النظر فقط في coroutines ووعود ، مزينة غير async/await
، كما وجود مشاكل في المناهج القديمة يؤكد عملية التطور نفسها.
هذان المصطلحان ليست متطابقة. على سبيل المثال ، في ECMAScript لا توجد أي coroutines ، ولكن هناك راحة نحوية لاستخدام Promise
، والذي بدوره ينظم العمل فقط مع جحيم رد الاتصال. في الواقع ، تذهب محركات البرمجة النصية مثل V8 إلى أبعد من ذلك وتقوم بإجراء تحسينات خاصة للوظائف والمكالمات غير async/await
.
co_async/co_await
الخبراء co_async/co_await
التي لم تندرج في C ++ 17 هنا على المورد ، ولكن الضغط من coroutines العملاقة البرمجيات يمكن أن تظهر في المعيار بالضبط في شكلها. في غضون ذلك ، فإن الحل المعترف به تقليديًا هو Boost.Context و Boost.Fiber و Boost.Coroutine2 .
في Java ، لا يوجد حتى الآن عدم async/await
على مستوى اللغة ، ولكن هناك حلول مثل EA Async ، والتي ، مثل Boost.Context ، تحتاج إلى تخصيصها لكل إصدار من JVM و بايت كود.
يحتوي Go على مكوناته الخاصة ، ولكن إذا نظرت بعناية في المقالات وتقارير الأخطاء الخاصة بالمشاريع المفتوحة ، فقد اتضح أن كل شيء هنا ليس سلسًا. ربما يكون فقدان واجهة coroutine ككيان مُدار ليس فكرة جيدة.
رأي الكاتب: إن الكورتيونات المعدنية العارية خطيرة
شخصيا ، المؤلف لديه القليل ضد coroutines في اللغات الديناميكية ، لكنه حذر للغاية من أي يمزح مع المكدس على مستوى رمز الجهاز.
بضع نقاط:
- المكدس المطلوب:
- المكدس في كومة الذاكرة المؤقتة له عدد من العيوب: مشاكل تحديد الفائض في الوقت المناسب ، والأضرار من قبل الجيران وغيرها من مشاكل الموثوقية / الأمن ،
- يتطلب المكدس المحمي صفحة واحدة على الأقل من الذاكرة الفعلية وصفحة شرطية إضافية ونفقات إضافية لكل استدعاء لوظائف عدم
async
: 4 + كيلوبايت (كحد أدنى) + زيادة حدود النظام ، - في نهاية المطاف ، قد لا يتم استخدام جزء كبير من الذاكرة المخصصة للمكدسات خلال فترة توقف عمل Coroutine.
- من الضروري تنفيذ منطق معقد لحفظ واستعادة وحذف حالة coroutines:
- لكل حالة من بنية المعالج (حتى النماذج) وواجهة ثنائية (ABI): على سبيل المثال ،
- تقدم ميزات البنية الجديدة أو الاختيارية مشاكل كامنة محتملة (مثل Intel TSX أو معالجات ARM المشتركة أو MIPS) ،
- مشاكل محتملة أخرى بسبب الوثائق المغلقة أنظمة الملكية (وثائق دفعة تشير إلى هذا).
- المشاكل المحتملة في أدوات التحليل الديناميكي والأمان بشكل عام:
- على سبيل المثال ، التكامل مع Valgrind مطلوب الكل بسبب نفس مداخن القفز ،
- من الصعب التحدث عن مضادات الفيروسات ، ولكن ربما لم يعجبهم ذلك حقًا في مثال مشاكل JVM في الماضي
- أنا متأكد من ظهور أنواع جديدة من الهجمات وسيتم الكشف عن نقاط الضعف المرتبطة بتنفيذ Coruteines.
رأي المؤلف: المولدات yield
الشر الأساسي
يرتبط هذا الموضوع الذي يبدو من جهة خارجية ارتباطًا مباشرًا بمفهوم كوروتينز وخاصية "الاستمرار".
باختصار ، يجب أن يوجد مكرر كامل لأي مجموعة. لماذا إنشاء مشكلة المولد - اقتصاص المولد غير واضح. على سبيل المثال ، الحالة ذات range()
في Python هي عرض حصري أكثر من كونها عذرًا للمضاعفات التقنية.
إذا كانت الحالة عبارة عن مولد لا نهائي ، فإن منطق تنفيذه أساسي. لماذا خلق صعوبات فنية إضافية من أجل دفع دورة مستمرة لا نهاية لها.
المبرر المعقول الوحيد الذي ظهر لاحقًا ، والذي تم إعطاؤه من قبل مؤيدي Coroutine ، هو جميع أنواع موزعي التدفق مع التحكم المقلوب. في الواقع ، هذه حالة متخصصة ضيقة لحل المشكلات الفردية على مستوى المكتبة ، وليس منطق الأعمال للتطبيقات. في الوقت نفسه ، هناك حل أنيق وبسيط وأكثر وصفًا من خلال أجهزة الحالة المحدودة. مجال هذه المشاكل التقنية أصغر بكثير من مجال منطق الأعمال الشائع.
في الواقع ، يتم الحصول على المشكلة التي يجب حلها من الإصبع وتتطلب جهودًا جادة نسبيًا للتنفيذ الأولي والدعم على المدى الطويل. لدرجة أن بعض المشاريع قد تفرض حظراً على استخدام Coruteines على مستوى رمز الآلة باتباع مثال الحظر على goto
أو استخدام تخصيص الذاكرة الديناميكية في الصناعات الفردية.
رأي المؤلف: نموذج وعد async/await
ECMAScript هو أكثر موثوقية ، لكنه يتطلب التكيف
على عكس التماثيل المستمرة ، في هذا النموذج ، يتم تقسيم أجزاء الكود سرا إلى كتل غير قابلة للمقاطعة مصممة كوظائف مجهولة. في C ++ ، هذا ليس مناسبًا تمامًا نظرًا لخصائص إدارة الذاكرة ، مثال:
struct SomeObject { using Value = std::vector<int>; Promise funcPromise() { return Promise.resolved(value_); } void funcCallback(std::function<void()> &&cb, const Value& val) { somehow_call_later(cb); } Value value_; }; Promise example() { SomeObject some_obj; return some_obj.funcPromise() .catch([](const std::exception &e){
أولاً ، سيتم تدمير some_obj
عند الخروج من example()
وقبل استدعاء وظائف لامدا.
ثانيًا ، وظائف لامدا مع متغيرات أو مراجع التقاط هي كائنات وإضافة نسخة / نقل سراً ، والتي يمكن أن تؤثر سلبًا على الأداء مع عدد كبير من الالتقاطات والحاجة إلى تخصيص ذاكرة في الكومة أثناء محو النوع في std::function
المعتادة.
ثالثًا ، تم تصميم واجهة Promise
نفسها وفقًا لمفهوم "الوعد" بالنتيجة ، بدلاً من التنفيذ المستمر لمنطق الأعمال.
قد يبدو الحل التخطيطي غير الأمثل شيئًا كالتالي:
Promise example() { struct LocalContext { SomeObject some_obj; }; auto ctx = std::make_shared<LocalContext>(); return some_obj.funcPromise() .catch([](const std::exception &e){
ملحوظة: std::move
بدلاً من std::shared_ptr
مناسب بسبب عدم القدرة على الانتقال إلى العديد من lambdas في نفس الوقت ونمو حجمها.
مع إضافة غير async/await
تأتي الرعب غير المتزامن في حالة قابلة للهضم:
async void example() { SomeObject some_obj; try { SomeObject::Value val = await some_obj.func(); } catch (const std::exception& e) (
رأي الكاتب: مخطط Coroutine هو تمثال نصفي
يصف بعض النقاد عدم وجود جدولة والاستخدام "غير النزيه" لموارد المعالج مشكلة. ربما تكون المشكلة الأكثر خطورة هي مكان البيانات والاستخدام الفعال لذاكرة التخزين المؤقت للمعالج.
فيما يتعلق بالمشكلة الأولى: يبدو أن تحديد الأولويات على مستوى الكائنات الحية الفردية يبدو وكأنه مبلغ كبير. بدلاً من ذلك ، يمكن تشغيلها بشكل مشترك لمهمة موحدة محددة. هذا ما تفعله تدفقات حركة المرور.
هذا ممكن عن طريق إنشاء مثيلات منفصلة من Event Loop مع خيوط "الحديد" الخاصة بها والتخطيط على مستوى نظام التشغيل. الخيار الثاني هو مزامنة coroutines مع بدائية نسبيا (موتكس ، خنق) بدائية من حيث المنافسة و / أو الأداء.
لا تجعل البرمجة غير المتزامنة موارد المعالج مرنة وتتطلب قيودًا طبيعية تمامًا على عدد المهام والقيود التي تتم معالجتها في وقت واحد على إجمالي وقت التنفيذ.
تتطلب الحماية من الحظر الطويل على روتين واحد نفس الإجراءات التي يتم اتباعها مع عمليات رد الاتصال - لتجنب حظر مكالمات النظام ودورات معالجة البيانات الطويلة.
تتطلب المشكلة الثانية البحث ، ولكن على الأقل مكدسات coroutine نفسها وتفاصيل تنفيذ Future / Promise تنتهك بالفعل موقع البيانات. هناك فرصة لمحاولة مواصلة تنفيذ نفس اللقاح إذا كان المستقبل مهمًا بالفعل. هناك حاجة إلى آلية معينة لحساب وقت التنفيذ أو عدد هذه الاستمرارات من أجل منع coroutine واحد من التقاط وقت المعالج بأكمله. قد لا يعطي هذا نتيجة ، أو يعطي نتيجة مزدوجة جدًا ، اعتمادًا على حجم ذاكرة التخزين المؤقت للمعالج وعدد سلاسل العمليات.
هناك أيضًا نقطة ثالثة - تسمح لك العديد من تطبيقات جدولة Coroutine بتشغيلها على نوى معالج مختلفة ، والتي على العكس تضيف مشاكل بسبب المزامنة الإلزامية عند الوصول إلى الموارد المشتركة. في حالة دفق حلقة حدث واحد ، تكون هذه المزامنة مطلوبة فقط على المستوى المنطقي ، منذ ذلك الحين كل كتلة رد متزامنة مضمونة للعمل بدون سباق مع الآخرين.
رأي المؤلف: كل شيء جيد في الاعتدال
وجود مؤشرات الترابط في أنظمة التشغيل الحديثة لا يلغي استخدام العمليات الفردية. أيضا ، معالجة عدد كبير من العملاء في "حلقة الأحداث" لا يلغي استخدام خيوط "الحديد" المعزولة للاحتياجات الأخرى.
على أي حال ، فإن coroutines ومتغيرات مختلفة من Loops Event تعقيد عملية التصحيح دون الدعم الضروري في الأدوات ، ومع المتغيرات المحلية على مكدس coroutine ، يصبح كل شيء أكثر صعوبة - لا توجد طريقة عمليًا للوصول إليها.
FutoIn AsyncSteps - بديل للكوروتينات
نأخذ كأساس لنمط حلقة الأحداث الراسخ بالفعل وتنظيم مخططات رد الاتصال وفقًا لنوع وعد ECMAScript (JavaScript).
فيما يتعلق بتخطيط التنفيذ ، نحن مهتمون بالأنشطة التالية من Event Loop:
Handle immediate(callack)
يتطلب مكدس مكالمات نظيف.- مؤجل رد الاتصال
Handle deferred(delay, callback)
. - قم
handle.cancel()
.
لذلك نحصل على واجهة تسمى AsyncTool
، والتي يمكن تنفيذها بطرق عديدة ، بما في ذلك على رأس التطورات الحالية المثبتة. ليس لديه علاقة مباشرة بكتابة منطق الأعمال ، لذلك لن ندخل في مزيد من التفاصيل.
شجرة الخطوات:
في مفهوم AsyncSteps ، يتم ترتيب شجرة مجردة من الخطوات المتزامنة وتنفيذها من خلال التعمق في تسلسل الإنشاء. يتم تعيين خطوات كل مستوى أعمق ديناميكيًا عند اكتمال هذا المقطع.
تتم جميع التفاعلات من خلال واجهة AsyncSteps
واحدة ، والتي يتم تمريرها ، حسب الاصطلاح ، كمعلمة أولى لكل خطوة. حسب الاصطلاح ، يكون اسم المعلمة asi
أو مهملًا as
. يتيح لك هذا النهج قطع الاتصال تمامًا تقريبًا بين تنفيذ معين وكتابة منطق الأعمال في المكونات الإضافية والمكتبات.
في عمليات التنفيذ الأساسية ، تتلقى كل خطوة AsyncSteps
الخاص بكائن ينفذ AsyncSteps
، مما يسمح بتتبع الأخطاء المنطقية في الوقت المناسب عند استخدام الواجهة.
مثال تجريدي:
asi.add( // Level 0 step 1 func( asi ){ print( "Level 0 func" ) asi.add( // Level 1 step 1 func( asi ){ print( "Level 1 func" ) asi.error( "MyError" ) }, onerror( asi, error ){ // Level 1 step 1 catch print( "Level 1 onerror: " + error ) asi.error( "NewError" ) } ) }, onerror( asi, error ){ // Level 0 step 1 catch print( "Level 0 onerror: " + error ) if ( error strequal "NewError" ) { asi.success( "Prm", 123, [1, 2, 3], true) } } ) asi.add( // Level 0 step 2 func( asi, str_param, int_param, array_param ){ print( "Level 0 func2: " + param ) } )
نتيجة التنفيذ:
Level 0 func 1 Level 1 func 1 Level 1 onerror 1: MyError Level 0 onerror 1: NewError Level 0 func 2: Prm
في المزامنة ، ستبدو كما يلي:
str_res, int_res, array_res, bool_res // undefined try { // Level 0 step 1 print( "Level 0 func 1" ) try { // Level 1 step 1 print( "Level 1 func 1" ) throw "MyError" } catch( error ){ // Level 1 step 1 catch print( "Level 1 onerror 1: " + error ) throw "NewError" } } catch( error ){ // Level 0 step 1 catch print( "Level 0 onerror 1: " + error ) if ( error strequal "NewError" ) { str_res = "Prm" int_res = 123 array_res = [1, 2, 3] bool_res = true } else { re-throw } } { // Level 0 step 2 print( "Level 0 func 2: " + str_res ) }
الحد الأقصى لمحاكاة الشفرة التقليدية المتزامنة يمكن رؤيته على الفور ، مما يساعد في سهولة القراءة.
من وجهة نظر منطق الأعمال ، تنمو مجموعة كبيرة من المتطلبات بمرور الوقت ، ولكن يمكننا تقسيمها إلى أجزاء يسهل فهمها. موصوف أدناه ، نتيجة تشغيل عمليا لمدة أربع سنوات.
واجهات برمجة التطبيقات الأساسية لوقت التشغيل:
add(func[, onerror])
- تقليد try-catch
.success([args...])
- إشارة صريحة على الانتهاء بنجاح:
- يعني بشكل افتراضي
- يمكن تمرير النتائج إلى الخطوة التالية.
error(code[, reason)
- انقطاع التنفيذ بسبب خطأ:
code
- لديه نوع سلسلة للتكامل بشكل أفضل مع بروتوكولات الشبكة في بنية الخدمات المصغرة ،reason
- شرح تعسفي لشخص.
state()
- تناظرية التخزين المحلي لمؤشر الترابط. المفاتيح النقابية المحددة مسبقًا:
error_info
- شرح الخطأ الأخير لشخص ما ،last_exception
- مؤشر إلى كائن الاستثناء الأخير ،async_stack
- مجموعة مكالمات غير متزامنة async_stack
ما تسمح التكنولوجيا ،- يتم تعيين الباقي من قبل المستخدم.
المثال السابق بالفعل مع كود C ++ الحقيقي وبعض الميزات الإضافية:
#include <futoin/iasyncsteps.hpp> using namespace futoin; void some_api(IAsyncSteps& asi) { asi.add( [](IAsyncSteps& asi) { std::cout << "Level 0 func 1" << std::endl; asi.add( [](IAsyncSteps& asi) { std::cout << "Level 1 func 1" << std::endl; asi.error("MyError"); }, [](IAsyncSteps& asi, ErrorCode code) { std::cout << "Level 1 onerror 1: " << code << std::endl; asi.error("NewError", "Human-readable description"); } ); }, [](IAsyncSteps& asi, ErrorCode code) { std::cout << "Level 0 onerror 1: " << code << std::endl; if (code == "NewError") { // Human-readable error info assert(asi.state().error_info == "Human-readable description"); // Last exception thrown is also available in state std::exception_ptr e = asi.state().last_exception; // NOTE: smart conversion of "const char*" asi.success("Prm", 123, std::vector<int>({1, 2, 3}, true)); } } ); asi.add( [](IAsyncSteps& asi, const futoin::string& str_res, int int_res, std::vector<int>&& arr_res) { std::cout << "Level 0 func 2: " << str_res << std::endl; } ); }
API لإنشاء حلقات:
loop( func, [, label] )
- خطوة بجسم قابل للتكرار بلا حدود.forEach( map|list, func [, label] )
- تكرار كائن المجموعة.repeat( count, func [, label] )
- عدد مرات تكرار التكرار المحدد.break( [label] )
تمثيلية لمقاطعة العروة التقليدية.continue( [label] )
هو تناظري لاستمرار الحلقة التقليدية مع تكرار جديد.
تقدم المواصفات أسماء بديلة breakLoop
و breakLoop
وغيرها في حالة التعارض مع الكلمات المحجوزة.
مثال C ++:
asi.loop([](IAsyncSteps& asi) {
API للتكامل مع الأحداث الخارجية:
setTimeout( timeout_ms )
- يلقي خطأ Timeout
بعد انتهاء المهلة إذا لم تكمل الخطوة setTimeout( timeout_ms )
الفرعية التنفيذ.setCancel( handler )
- يقوم بتعيين معالج الإلغاء ، والذي يتم استدعاؤه عندما يتم إلغاء مؤشر الترابط تمامًا وعندما يتم توسيع رزمة الخطوات غير المتزامنة أثناء معالجة الأخطاء.waitExternal()
- انتظار بسيط لحدث خارجي.
- ملاحظة: إنه آمن للاستخدام فقط في التقنيات مع جامع القمامة.
استدعاء أي من هذه الوظائف يجعل استدعاء صريح success()
ضروريًا.
مثال C ++:
asi.add([](IAsyncSteps& asi) { auto handle = schedule_external_callback([&](bool err) { if (err) { try { asi.error("ExternalError"); } catch (...) {
مثال ECMAScript:
asi.add( (asi) => { asi.waitExternal();
واجهة برمجة تطبيقات Integration / Promise Integration:
await(promise_future[, on_error])
- في انتظار المستقبل / الوعد كخطوة.promise()
- يحول تدفق التنفيذ بالكامل إلى المستقبل / الوعد ، ويستخدم بدلاً من execute()
.
مثال C ++:
[](IAsyncSteps& asi) { // Proper way to create new AsyncSteps instances // without hard dependency on implementation. auto new_steps = asi.newInstance(); new_steps->add([](IAsyncSteps& asi) {}); // Can be called outside of AsyncSteps event loop // new_steps.promise().wait(); // or // new_steps.promise<int>().get(); // Proper way to wait for standard std::future asi.await(new_steps->promise()); // Ensure instance lifetime asi.state()["some_obj"] = std::move(new_steps); };
واجهة برمجة تطبيقات التحكم في تدفق منطق الأعمال:
AsyncSteps(AsyncTool&)
هو مُنشئ يربط سلسلة تنفيذ AsyncSteps(AsyncTool&)
حدث معينة.execute()
- لبدء مؤشر ترابط التنفيذ.cancel()
- يلغي موضوع التنفيذ.
مطلوب بالفعل تنفيذ واجهة معينة هنا.
مثال C ++:
#include <futoin/ri/asyncsteps.hpp> #include <futoin/ri/asynctool.hpp> void example() { futoin::ri::AsyncTool at; futoin::ri::AsyncSteps asi{at}; asi.loop([&](futoin::IAsyncSteps &asi){ // Some infinite loop logic }); asi.execute(); std::this_thread::sleep_for(std::chrono::seconds{10}); asi.cancel(); // called in d-tor by fact }
واجهات برمجة التطبيقات الأخرى:
newInstance()
- يسمح لك بإنشاء خيط تنفيذ جديد دون الاعتماد المباشر على التنفيذ.sync(object, func, onerror)
- نفس الشيء ، ولكن مع المزامنة بالنسبة لكائن يقوم بتنفيذ الواجهة المقابلة.parallel([on_error])
- add()
خاصة add()
، وخطواتها الفرعية هي تيارات AsyncSteps منفصلة:
- كل الخيوط لها
state()
مشتركة state()
، - الخيط الأصل يستمر في التنفيذ عند الانتهاء من جميع الأطفال
- خطأ غير مقصود في أي طفل يلغي جميع سلاسل العمليات الفرعية الأخرى على الفور.
أمثلة C ++:
#include <futoin/ri/mutex.hpp> using namespace futoin; ri::Mutex mtx_a; void sync_example(IAsyncSteps& asi) { asi.sync(mtx_a, [](IAsyncSteps& asi) { // synchronized section asi.add([](IAsyncSteps& asi) { // inner step in the section // This synchronization is NOOP for already // acquired Mutex. asi.sync(mtx_a, [](IAsyncSteps& asi) { }); }); }); } void parallel_example(IAsyncSteps& asi) { using OrderVector = std::vector<int>; asi.state("order", OrderVector{}); auto& p = asi.parallel([](IAsyncSteps& asi, ErrorCode) { // Overall error handler asi.success(); }); p.add([](IAsyncSteps& asi) { // regular flow asi.state<OrderVector>("order").push_back(1); asi.add([](IAsyncSteps& asi) { asi.state<OrderVector>("order").push_back(4); }); }); p.add([](IAsyncSteps& asi) { asi.state<OrderVector>("order").push_back(2); asi.add([](IAsyncSteps& asi) { asi.state<OrderVector>("order").push_back(5); asi.error("SomeError"); }); }); p.add([](IAsyncSteps& asi) { asi.state<OrderVector>("order").push_back(3); asi.add([](IAsyncSteps& asi) { asi.state<OrderVector>("order").push_back(6); }); }); asi.add([](IAsyncSteps& asi) { asi.state<OrderVector>("order"); // 1, 2, 3, 4, 5 }); };
الأوليات القياسية للتزامن
Mutex
- يقيد التنفيذ المتزامن لخيوط N
مع قائمة انتظار في Q
، افتراضيًا N=1, Q=unlimited
.Throttle
- يحد من عدد المدخلات N
في الفترة P
بقائمة انتظار في Q
، افتراضيًا N=1, P=1s, Q=0
.Limiter
هو مزيج من Mutex
و Throttle
، والذي يُستخدم عادةً عند إدخال معالجة الطلبات الخارجية وعند استدعاء الأنظمة الخارجية لغرض التشغيل المستقر تحت الحمل.
في حالة DefenseRejected
حدود قائمة الانتظار ، يتم DefenseRejected
خطأ DefenseRejected
، والذي يتضح معناه من وصف Limiter
.
الفوائد الرئيسية
لم يكن مفهوم AsyncSteps غاية في حد ذاته ، ولكنه ولد من الحاجة إلى تنفيذ غير متزامن للبرامج أكثر تحكمًا من حيث المهلة الزمنية والإلغاء والاتصال العام لرد المكالمات الفردية. لا يوجد أي من الحلول العالمية في ذلك الوقت وتوفر الآن نفس الوظيفة. لذلك:
FTN12 — .
setCancel()
— . , . RAII atexit()
.
cancel()
— , . SIGTERM
pthread_cancel()
, .
setTimeout()
— . , "Timeout".
— FutoIn AsyncSteps .
— ABI , . Embedded MMU.
Intel Xeon E3-1245v2/DDR1333 Debian Stretch .
:
- Boost.Fiber
protected_fixedsize_stack
. - Boost.Fiber
pooled_fixedsize_stack
. - FutoIn AsyncSteps .
- FutoIn AsyncSteps (
FUTOIN_USE_MEMPOOL=false
).
- FutoIn NitroSteps<> — .
Boost.Fiber :
- 1 . .
- 30 . 1 . .
- 30 .
mmap()/mprotect()
boost::fiber::protected_fixedsize_stack
. - .
- 30 . 10 . .
"" , .. , . . .
GCC 6.3.0. lang tcmalloc , .
GitHub GitLab .
1.
| | |
---|
Boost.Fiber protected | 4.8s | 208333.333Hz |
Boost.Fiber pooled | 0.23s | 4347826.086Hz |
FutoIn AsyncSteps | 0.21s | 4761904.761Hz |
FutoIn AsyncSteps no mempool | 0.31s | 3225806.451Hz |
FutoIn NitroSteps | 0.255s | 3921568.627Hz |
— .
Boost.Fiber - , pooled_fixedsize_stack
, AsyncSteps.
2.
| | |
---|
Boost.Fiber protected | 6.31s | 158478.605Hz |
Boost.Fiber pooled | 1.558s | 641848.523Hz |
FutoIn AsyncSteps | 1.13s | 884955.752Hz |
FutoIn AsyncSteps no mempool | 1.353s | 739098.300Hz |
FutoIn NitroSteps | 1.43s | 699300.699Hz |
— .
, . , — .
3.
| | |
---|
Boost.Fiber protected | 5.096s | 1962323.390Hz |
Boost.Fiber pooled | 5.077s | 1969667.126Hz |
FutoIn AsyncSteps | 5.361s | 1865323.633Hz |
FutoIn AsyncSteps no mempool | 8.288s | 1206563.706Hz |
FutoIn NitroSteps | 3.68s | 2717391.304Hz |
— .
, Boost.Fiber AsyncSteps, NitroSteps.
| |
---|
Boost.Fiber protected | 124M |
Boost.Fiber pooled | 505M |
FutoIn AsyncSteps | 124M |
FutoIn AsyncSteps no mempool | 84M |
FutoIn NitroSteps | 115M |
— .
, Boost.Fiber .
: Node.js
- Promise
: + 10 . . 10 . JIT NODE_ENV=production
, @futoin/optihelp
.
GitHub GitLab . Node.js v8.12.0 v10.11.0, FutoIn CID .
Tech | Simple | Loop |
---|
Node.js v10 | | |
FutoIn AsyncSteps | 1342899.520Hz | 587.777Hz |
async/await | 524983.234Hz | 630.863Hz |
Node.js v8 | | |
FutoIn AsyncSteps | 682420.735Hz | 588.336Hz |
async/await | 365050.395Hz | 400.575Hz |
— .
async/await
? , V8 Node.js v10 .
, Promise async/await
Node.js Event Loop. ( ), FutoIn AsyncSteps .
AsyncSteps Node.js Event Loop async/await
- Node.js v10.
, ++ — . , Node.js 10 .
الاستنتاجات
C++, FutoIn AsyncSteps Boost.Fiber , Boost.Fiber mmap()/mprotect
.
, - , . .
FutoIn AsyncSteps JavaScript async/await
Node.js v10.
, -, . .
- "" . — API.
الخلاصة
, FutoIn AsyncSteps , "" async/await
. , . Promise
ECMAScript, AsyncSteps "" .
. AsyncSteps NitroSteps .
, - .
Java/JVM — . .
, GitHub / GitLab .