مقدمة في برمجة بايثون غير المتزامنة

مرحبا. أعدت ترجمة مقال شيق عشية بدء الدورة الأساسية "Python Developer" .








المقدمة



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







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



على سبيل المثال ، بدلاً من انتظار اكتمال طلب HTTP قبل متابعة التنفيذ ، يمكنك إرسال الطلب والقيام بعمل آخر ينتظر في الطابور باستخدام Coroutines غير المتزامنة في Python.



يعد Asynchrony أحد الأسباب الرئيسية لاختيار Node.js لتطبيق الواجهة الخلفية. تعتمد الكمية الكبيرة من التعليمات البرمجية التي نكتبها ، خاصة في التطبيقات ذات الإدخال / الإخراج الثقيل ، مثل مواقع الويب ، على الموارد الخارجية. يمكن أن يحتوي على أي شيء من مكالمة قاعدة بيانات بعيدة إلى طلبات POST إلى خدمة REST. بمجرد إرسال طلب إلى أحد هذه الموارد ، ستنتظر التعليمات البرمجية الخاصة بك ببساطة الرد. باستخدام البرمجة غير المتزامنة ، يمكنك السماح للتعليمة البرمجية الخاصة بك بمعالجة المهام الأخرى أثناء انتظار استجابة من الموارد.



كيف تمكنت بايثون من القيام بأشياء متعددة في نفس الوقت؟







1. عمليات متعددة



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



from multiprocessing import Process

def print_func(continent='Asia'):
    print('The name of continent is : ', continent)

if __name__ == "__main__":  # confirms that the code is under main function
    names = ['America', 'Europe', 'Africa']
    procs = []
    proc = Process(target=print_func)  # instantiating without any argument
    procs.append(proc)
    proc.start()

    # instantiating process with arguments
    for name in names:
        # print(name)
        proc = Process(target=print_func, args=(name,))
        procs.append(proc)
        proc.start()

    # complete the processes
    for proc in procs:
        proc.join()


انتاج:



The name of continent is :  Asia
The name of continent is :  America
The name of continent is :  Europe
The name of continent is :  Africa


2. خيوط متعددة



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



import threading
 
def print_cube(num):
    """
    function to print cube of given num
    """
    print("Cube: {}".format(num * num * num))
 
def print_square(num):
    """
    function to print square of given num
    """
    print("Square: {}".format(num * num))
 
if __name__ == "__main__":
    # creating thread
    t1 = threading.Thread(target=print_square, args=(10,))
    t2 = threading.Thread(target=print_cube, args=(10,))
 
    # starting thread 1
    t1.start()
    # starting thread 2
    t2.start()
 
    # wait until thread 1 is completely executed
    t1.join()
    # wait until thread 2 is completely executed
    t2.join()
 
    # both threads completely executed
    print("Done!")


انتاج:



Square: 100
Cube: 1000
Done!


3. Coroutines و yield:



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



def print_name(prefix):
    print("Searching prefix:{}".format(prefix))
    try : 
        while True:
                # yeild used to create coroutine
                name = (yield)
                if prefix in name:
                    print(name)
    except GeneratorExit:
            print("Closing coroutine!!")
 
corou = print_name("Dear")
corou.__next__()
corou.send("James")
corou.send("Dear James")
corou.close()


انتاج:



Searching prefix:Dear
Dear James
Closing coroutine!!


4. البرمجة غير المتزامنة



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



الجواب: asyncio



Asyncio- وحدة البرمجة غير المتزامنة التي تم تقديمها في Python 3.4. تم تصميمه لاستخدام coroutines والعقود الآجلة لتسهيل كتابة التعليمات البرمجية غير المتزامنة ويجعلها قابلة للقراءة مثل التعليمات البرمجية المتزامنة تقريبًا بسبب نقص عمليات الاسترجاعات.



Asyncioيستخدم الانشاءات المختلفة :، event loopcoroutines و future.



  • event loop . .
  • ( ) – , Python, await event loop. event loop. Tasks, Future.
  • Future , . exception.


وبمساعدة asyncioمنك ، يمكنك تنظيم التعليمات البرمجية الخاصة بك بحيث يتم تعريف المهام الفرعية على أنها coroutines وتسمح لك بالتخطيط لإطلاقها بالطريقة التي تريدها ، بما في ذلك في نفس الوقت. تحتوي Coroutines على نقاط yieldنحدد فيها نقاط تبديل السياق المحتملة. إذا كانت هناك مهام في قائمة انتظار الانتظار ، فسيتم تبديل السياق ؛ وإلا ، لا.



مفتاح السياق asyncioهو event loopالذي ينقل تدفق التحكم من كوروتين إلى آخر.



في المثال التالي ، نقوم بتشغيل 3 مهام غير متزامنة تقوم بشكل فردي بتقديم طلبات إلى Reddit واسترداد وإخراج محتوى JSON. نستخدم aiohttp - مكتبة العميل http ، التي تضمن تنفيذ حتى طلب HTTP بشكل غير متزامن.



import signal  
import sys  
import asyncio  
import aiohttp  
import json

loop = asyncio.get_event_loop()  
client = aiohttp.ClientSession(loop=loop)

async def get_json(client, url):  
    async with client.get(url) as response:
        assert response.status == 200
        return await response.read()

async def get_reddit_top(subreddit, client):  
    data1 = await get_json(client, 'https://www.reddit.com/r/' + subreddit + '/top.json?sort=top&t=day&limit=5')

    j = json.loads(data1.decode('utf-8'))
    for i in j['data']['children']:
        score = i['data']['score']
        title = i['data']['title']
        link = i['data']['url']
        print(str(score) + ': ' + title + ' (' + link + ')')

    print('DONE:', subreddit + '\n')

def signal_handler(signal, frame):  
    loop.stop()
    client.close()
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)

asyncio.ensure_future(get_reddit_top('python', client))  
asyncio.ensure_future(get_reddit_top('programming', client))  
asyncio.ensure_future(get_reddit_top('compsci', client))  
loop.run_forever()


انتاج:



50: Undershoot: Parsing theory in 1965 (http://jeffreykegler.github.io/Ocean-of-Awareness-blog/individual/2018/07/knuth_1965_2.html)
12: Question about best-prefix/failure function/primal match table in kmp algorithm (https://www.reddit.com/r/compsci/comments/8xd3m2/question_about_bestprefixfailure_functionprimal/)
1: Question regarding calculating the probability of failure of a RAID system (https://www.reddit.com/r/compsci/comments/8xbkk2/question_regarding_calculating_the_probability_of/)
DONE: compsci

336: /r/thanosdidnothingwrong -- banning people with python (https://clips.twitch.tv/AstutePluckyCocoaLitty)
175: PythonRobotics: Python sample codes for robotics algorithms (https://atsushisakai.github.io/PythonRobotics/)
23: Python and Flask Tutorial in VS Code (https://code.visualstudio.com/docs/python/tutorial-flask)
17: Started a new blog on Celery - what would you like to read about? (https://www.python-celery.com)
14: A Simple Anomaly Detection Algorithm in Python (https://medium.com/@mathmare_/pyng-a-simple-anomaly-detection-algorithm-2f355d7dc054)
DONE: python

1360: git bundle (https://dev.to/gabeguz/git-bundle-2l5o)
1191: Which hashing algorithm is best for uniqueness and speed? Ian Boyd's answer (top voted) is one of the best comments I've seen on Stackexchange. (https://softwareengineering.stackexchange.com/questions/49550/which-hashing-algorithm-is-best-for-uniqueness-and-speed)
430: ARM launches “Facts” campaign against RISC-V (https://riscv-basics.com/)
244: Choice of search engine on Android nuked by “Anonymous Coward” (2009) (https://android.googlesource.com/platform/packages/apps/GlobalSearch/+/592150ac00086400415afe936d96f04d3be3ba0c)
209: Exploiting freely accessible WhatsApp data or “Why does WhatsApp web know my phone’s battery level?” (https://medium.com/@juan_cortes/exploiting-freely-accessible-whatsapp-data-or-why-does-whatsapp-know-my-battery-level-ddac224041b4)
DONE: programming


استخدام Redis و Redis Queue RQ



باستخدام asyncioوهو aiohttpليس دائما فكرة جيدة، خاصة إذا كنت تستخدم الإصدارات القديمة من بيثون. بالإضافة إلى ذلك ، هناك أوقات تحتاج فيها إلى توزيع المهام عبر خوادم مختلفة. في هذه الحالة ، يمكن استخدام RQ (Redis Queue). هذه مكتبة Python شائعة لإضافة وظائف إلى قائمة الانتظار ومعالجتها من قبل العاملين في الخلفية. لتنظيم قائمة الانتظار ، يتم استخدام Redis - قاعدة بيانات للمفاتيح / القيم.



في المثال أدناه ، أضفنا وظيفة بسيطة إلى قائمة الانتظار count_words_at_urlباستخدام Redis.



from mymodule import count_words_at_url
from redis import Redis
from rq import Queue


q = Queue(connection=Redis())
job = q.enqueue(count_words_at_url, 'http://nvie.com')


******mymodule.py******

import requests

def count_words_at_url(url):
    """Just an example function that's called async."""
    resp = requests.get(url)

    print( len(resp.text.split()))
    return( len(resp.text.split()))


انتاج:



15:10:45 RQ worker 'rq:worker:EMPID18030.9865' started, version 0.11.0
15:10:45 *** Listening on default...
15:10:45 Cleaning registries for queue: default
15:10:50 default: mymodule.count_words_at_url('http://nvie.com') (a2b7451e-731f-4f31-9232-2b7e3549051f)
322
15:10:51 default: Job OK (a2b7451e-731f-4f31-9232-2b7e3549051f)
15:10:51 Result is kept for 500 seconds


خاتمة



كمثال ، لنأخذ معرض الشطرنج حيث يتنافس أحد أفضل لاعبي الشطرنج ضد عدد كبير من الناس. لدينا 24 لعبة و 24 شخصًا يمكنك اللعب بها ، وإذا لعب لاعب الشطرنج معهم بشكل متزامن ، فسيستغرق الأمر 12 ساعة على الأقل (بافتراض أن اللعبة العادية تستغرق 30 حركة ، يفكر لاعب الشطرنج في الحركة لمدة 5 ثوانٍ ، و الخصم - حوالي 55 ثانية.) ومع ذلك ، في الوضع غير المتزامن ، سيكون لاعب الشطرنج قادرًا على القيام بحركة وترك وقت الخصم للتفكير ، في غضون ذلك ، بالانتقال إلى الخصم التالي وتقسيم الحركة. وبالتالي ، يمكنك القيام بحركة في جميع المباريات الـ 24 في دقيقتين ، ويمكن الفوز بها في ساعة واحدة فقط.



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



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



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






.







All Articles