AsyncIO Micropython: طرق المزامنة في البرمجة غير المتزامنة

في البرمجة المتسلسلة ، أواجه دائمًا الرغبة الواضحة في عدم إيقاف البرنامج في وقت يكون فيه هدف بعض المهام (العمليات) هو إجراءات دورية - على سبيل المثال ، قيم مستشعر الاقتراع ، أو نقل البيانات في جدول إلى خادم ، أو إدخال / إخراج كمية كبيرة من البيانات. أبسط شيء ، بالطبع ، هو انتظار الانتهاء من هذا الحدث الدوري ثم ، ببطء ، الاستمرار في أداء المهام الأخرى.

while True: do_ext_proc_before() do_internal_proc() sleep(5) do_ext_proc_after() 

يمكنك التخلي عن "sleep ()" وتمكين التحقق من بعض الحالات في الحلقة ، مما سيتيح لك عدم تأخير الحلقة الرئيسية على الأقل حتى يحدث حدث دوري:

  start = time() set_timer(start,wait=5) #   set_timeout(start,wait_to=7) #   set_irq(alarm) #    while True: curTime = time() do_ext_proc_before() if timer(curTime) or timeout(curTime) or alarm: # if all events crazy start simultaneously - reset all start = time() set_timer(start,wait=5) #   set_timeout(start,wait_to=7) #   set_irq(alarm) #    do_internal_proc() do_ext_proc_after() 

في البرمجة غير المتزامنة ، تصبح كل مهمة عملية مستقلة ويتم تنفيذها ، اعتمادًا على التنفيذ المحدد ، بالتوازي أو المتوازي ، باستخدام فهم داخلي لظروف الانتظار الطبيعية أو المصطنعة أو استخدام مورد محدود ، على سبيل المثال ، قرص أو قناة اتصال.

  setTask(do_ext_proc_before()) setTask(do_internal_proc(),timer=5,timeout=7,alarm_handler=alarm) setTask(do_ext_proc_after()) runTasks() 

الآن تنشأ مشكلة لا وجود لها في البرمجة المتسلسلة - ماذا تفعل إذا كان ذلك ضروريًا لمزامنة بعض العمليات مع عملياتها غير المتزامنة
تفعل؟ على سبيل المثال ، بعد تلقي البيانات من أجهزة الاستشعار ، ابدأ عملية إرسال البيانات إلى خادم أو الاستجابة لحالات الطوارئ. علاوة على ذلك ، في البرمجة غير المتزامنة ، يتم تنظيم تنظيم الإدخال / الإخراج غير المتزامن بشكل أساسي في معيار اللغة ، ويتم حل المواقف الأخرى في المكتبات.

لقد درست هذا السؤال باستخدام مكتبة موسوعة غير متزامنة Micropython المنشورة
بيتر هينش ( https://github.com/peterhinch/micropython-async/blob/master/TUTORIAL.md )
الحل الأبسط هو الإشارة إلى الحدث إلى العمليات المهتمة. للقيام بذلك ، استخدم فئة Event () ، التي تحتوي على عدة وحدات نمطية

  Event.Set( timeout = None, data = None ) -    (Event = True), ,     , Event.IsSet() - ,   ,  True,    False   Event.Wait() -   ,     - Done,Timeout,Cancel Event.Data() -  ,     Event.Clear() -   (Event = False). 

يتم تسجيل الإكمال ، كقاعدة عامة ، من خلال العملية التي تنتظر حدوث الحدث ، على سبيل المثال ، عملية العرض على الشاشة أو عملية حفظ البيانات على القرص ، أو المهلة ، فلا داعي لتحديث البيانات أو حفظها ، حيث لا يتم تحديثها لأي سبب ، أو بسبب انقطاعه عند وقوع حدث مهم آخر ، على سبيل المثال ، الانتقال إلى وضع السكون أو إعادة التشغيل ، مما قد يتطلب الإفراج عن جميع العمليات المعلقة عن طريق إعادة ضبط الأحداث المقابلة.

يجب أن يؤخذ في الاعتبار أنه من المستحسن جعل Event.Clear () مع عملية واحدة فقط ، إذا كان هذا لا يتعارض مع الخوارزمية المحددة. خلاف ذلك ، إذا كانت هناك عدة عمليات تنتظر حدوث Event.Set () ، فمن المفترض أن يتم إجراء Event.Clear () بواسطة إحدى العمليات المهتمة ، مع التأكد فقط من استجابة جميع العمليات المهتمة للحدث. يؤدي ذلك إلى تعقيد منطق القرار عند استخدام فئة الأحداث عند انتظار الحدث من خلال عدة عمليات. يتم حل هذا الموقف عن طريق إنشاء مقدار معين من Clear () للحدث الذي حدث.

  Barrier.Set( quantity = 1, timeout = None, data = None ) - quantity = 1  Event.Set() Barrier.IsSet() - ,   ,  True,    False   Barrier.Wait() -   ,     - Done,Timeout,Cancel Barrier.Data() -  ,     Barrier.qty -      Barrier.Clear() -   (Event = False),        Barrier.quantity  ,    ,     

في الوقت نفسه ، لا يتم الاحتفاظ بأي محاسبة - أي العملية المحددة التي استجابت بالفعل ، والتي لم تستجب بعد ، والتي قد تثير مشكلة إعادة التفاعل مع الحدث ، إذا كان هذا ضروريًا لخوارزمية معينة. إذا قمت بدلاً من Barrier.quantity بتمرير قائمة بأسماء العمليات المهتمة ، فيمكن تجنب هذا التعارض. أيضًا ، في حالة انتهاء المهلة أو مقاطعة الحدث ، يمكنك تحديد العمليات المعلقة المحددة التي لم تنجح بعد. ينطبق كل ما سبق على موقف حيث تنتظر عملية أو أكثر حدوث حدث معين أو موقف واحد لكثير. يحدث هذا عندما يتم تنفيذ العملية أو العمليات do_ext_proc_after () أثناء البرمجة المتسلسلة فقط بعد الانتهاء من do_internal_proc (). من أجل راحة أكثر تفهمًا ، سنقوم بتوسيع فئة الأحداث وطبقة الحاجز الحالية إلى فئة EEvent الجديدة وجعلها أو الكائنات الناتجة عنها - عالمية. في ما يلي "المبدعين" هو اسم أو قائمة أسماء العمليات التي تشعل الحدث أو تطلق العنان للمورد ، و "المتابعين" هو اسم أو قائمة أسماء العمليات التي تنتظر الحدث أو تطلق العنان للمورد

  EEvent.Set (creators, folowers, timeout = None, data = None ) -  True,        EEvent.IsSet( procName ) - procName -   ID   EEvent.Wait( procName ) EEvent.Clear( procName ) EEvent.Folowers() -    ,      . Barrier.qty = len(EEvent.List()) EEvent.Creators() -   ,     

باستخدام وحدات فئة EEvent ، يمكننا وصف حل المشكلة التي تمت مناقشتها مسبقًا.

  def do_internal_proc(): ... EEvent.Set ('p_Creator',('p_Folwer1','p_Folwer2')) # exec 'p_Folwer1','p_Folwer2' after event is come in 'p_Creator' ... def do_ext_proc_after1() ... EEvent.Wait('p_Creator') ... EEvent.Clear('p_Folwer1') def do_ext_proc_after1() ... EEvent.Wait('p_Creator') ... EEvent.Clear('p_Folwer2') 

فكر في الموقف المعاكس - عندما تنتظر إحدى العمليات اكتمال العديد من الأحداث ، أو حالة "كثير إلى واحد". بمعنى آخر ، إذا كان تنفيذ do_internal_proc () يمكن أن يكون إلا بعد تنفيذ do_ext_proc_before (). في الحالة القصوى ، عندما تنتظر إحدى العمليات اكتمال / حدوث حدث واحد ، يمكن حل المهمة باستخدام فئة الأحداث. عند توقع اكتمال العديد من الأحداث ، على سبيل المثال ، فقط بعد عرض البيانات المستلمة وإرسالها إلى الخادم وحفظها على القرص ، من الضروري أن تحدد كل عملية يتم تنفيذها مشاركتها في الحدث المتوقع والانتظار حتى تكتمل جميع العمليات.

  def do_ext_proc_before1() ... EEvent.Set('p_Creator1','p_Folwer') ... def do_ext_proc_before2() ... EEvent.Set('p_Creator2','p_Folwer') ... def do_internal_proc(): ... EEvent.Wait(('p_Creator1','p_Creator2')) ... EEvent.Clear('p_Folwer') 

جانب آخر مهم من البرمجة غير المتزامنة هو مشاركة مورد محدود. على سبيل المثال ، يجب أن يتم تحديث البيانات من خلال عملية واحدة فقط ، يجب أن تنتظر بقية العمليات التي تدعي إجراءً مماثلاً أو تنتظر حتى يتم تحديث البيانات. في الوقت نفسه ، من المحتمل ألا تكون قراءة البيانات للعرض أو إعادة التوجيه مهمة. لذلك ، من الضروري معرفة قائمة العمليات المتنافسة عند تنظيم الأحداث ذات الصلة.

في معيار البرمجة غير المتزامن ، يتم حل هذه المهمة بواسطة وحدات قفل الفئة. في الحالة العامة ، يمكن أيضًا حل المشكلة بشكل مشابه للحالة الفردية.

  def do_internal_proc(): # lock activity all 'followers' in list ... EEvent.Set ('p_Creator',('p_Folwer1','p_Folwer2')) # exec 'p_Folwer1','p_Folwer2' after event is come in 'p_Creator' ... def do_ext_proc_after1() ... EEvent.Wait('p_Creator') # waiting for recourse releale if ( EEvent.Set ('p_Folwer1','p_Folwer2')): # lock resource 'p_Folower1' now is 'p_Creator' ... else: EEvent.Wait('p_Folower2') # continue waiting for recourse releale ... EEvent.Clear('p_Folwer1') # releafe recourse def do_ext_proc_after1() ... EEvent.Wait('p_Creator') if ( EEvent.Set ('p_Folwer2','p_Folwer1')): # lock resource 'p_Folower2' now is 'p_Creator' ... else: EEvent.Wait('p_Folower1') # continue waiting for recourse releale ... EEvent.Clear('p_Folwer2') # releafe recourse 

بالإضافة إلى الخيارات التي تم النظر فيها ، هناك حلول تحد من الإنتاجية وتنظم قوائم الانتظار والجدولة الزمنية للعمليات ، لكن في نشاطي لم تكن هناك حاجة لذلك ، ونتيجة لذلك ، هناك حاجة إلى فهم كافٍ لنفسي ، على الرغم من أنني لا أستبعد أن تكون أكثر أناقة أو القرارات الاقتصادية.

في الختام ، أود أن أقول إن المناهج المتسلسلة وغير المتزامنة لها الحق المتساوي في الوجود وتنفيذ الخوارزميات المحددة بنجاح. لذلك ، يتم تحديد تطبيق هذا النهج أو ذاك حسب أولويات المنشئ - وهو الأمر الأكثر أهمية بالنسبة له عند تطبيق الخوارزميات المعينة - الشفافية وسهولة القراءة ، السرعة أو حجم الكود الناتج.

Source: https://habr.com/ru/post/ar442268/


All Articles