وبلاگ رسانگار
با ما حرفه ای باشید

سرور مجازی NVMe

نمای کلی Async IO در پایتون 3.7

0 55
زمان لازم برای مطالعه: 8 دقیقه


پایتون 3 asyncio ماژول ابزارهای اساسی برای پیاده سازی I/O ناهمزمان در پایتون فراهم می کند. این ماژول در پایتون 3.4 معرفی شد و با هر انتشار جزئی بعدی، ماژول به طور قابل توجهی تکامل یافته است.

این آموزش شامل یک نمای کلی از پارادایم ناهمزمان و روش پیاده سازی آن در پایتون 3.7 است.

Blocking در مقابل Non-Blocking I/O

مشکلی که Asynchrony به دنبال حل آن است این است مسدود کردن I/O.

به طور پیش فرض، زمانی که برنامه شما به داده ها از منبع ورودی/خروجی دسترسی پیدا می کند، قبل از ادامه اجرای برنامه منتظر می ماند تا عملیات تکمیل شود.

with open('myfile.txt', 'r') as file:
    data = file.read()
    
print(data)

برنامه است مسدود از ادامه جریان اجرای آن در حین دسترسی به دستگاه فیزیکی و انتقال داده ها.

عملیات شبکه یکی دیگر از منابع رایج مسدود کردن است:


import requests

req = requests.get('https://www.rasanegar.com/')





print(req.text)

در بسیاری از موارد، تاخیر ناشی از مسدود شدن ناچیز است. با این حال، مسدود کردن مقیاس های I/O بسیار ضعیف است. اگر باید تا 10 صبر کنید10 خواندن فایل یا تراکنش های شبکه، عملکرد آسیب خواهد دید.

پردازش چندگانه، Threading و Asynchrony

استراتژی‌ها برای به حداقل رساندن تاخیرهای مسدود کردن ورودی/خروجی به سه دسته اصلی تقسیم می‌شوند: چند پردازشی، رشته‌ای و ناهمزمانی.

پردازش چندگانه

پردازش چندگانه شکلی از محاسبات موازی است: دستورالعمل ها در یک بازه زمانی همپوشانی اجرا می شوند. روی چندین پردازنده فیزیکی یا هسته هر یک process ایجاد شده توسط هسته هزینه های سربار را متحمل می شود، از جمله یک تکه حافظه اختصاص داده شده به طور مستقل (هپ).

پایتون موازی سازی را با multiprocessing مدول.

در زیر نمونه‌ای از برنامه پایتون 3 است که چهار فرآیند فرزند را ایجاد می‌کند، که هر کدام یک تاخیر تصادفی و مستقل را نشان می‌دهند. خروجی نشان می دهد process شناسه هر فرزند، زمان سیستم قبل و بعد از هر تاخیر و تخصیص حافظه فعلی و پیک در هر مرحله.

from multiprocessing import Process
import os, time, datetime, random, tracemalloc

tracemalloc.start()
children = 4    
maxdelay = 6    

def status():
    return ('Time: ' + 
        str(datetime.datetime.now().time()) +
        '\t Malloc, Peak: ' +
        str(tracemalloc.get_traced_memory()))

def child(num):
    delay = random.randrange(maxdelay)
    print(f"{status()}\t\tProcess {num}, PID: {os.getpid()}, Delay: {delay} seconds...")
    time.sleep(delay)
    print(f"{status()}\t\tProcess {num}: Done.")

if __name__ == '__main__':
    print(f"Parent PID: {os.getpid()}")
    for i in range(children):
        proc = Process(target=child, args=(i,))
        proc.start()

خروجی:

Parent PID: 16048
Time: 09:52:47.014906    Malloc, Peak: (228400, 240036)     Process 0, PID: 16051, Delay: 1 seconds...
Time: 09:52:47.016517    Malloc, Peak: (231240, 240036)     Process 1, PID: 16052, Delay: 4 seconds...
Time: 09:52:47.018786    Malloc, Peak: (231616, 240036)     Process 2, PID: 16053, Delay: 3 seconds...
Time: 09:52:47.019398    Malloc, Peak: (232264, 240036)     Process 3, PID: 16054, Delay: 2 seconds...
Time: 09:52:48.017104    Malloc, Peak: (228434, 240036)     Process 0: Done.
Time: 09:52:49.021636    Malloc, Peak: (232298, 240036)     Process 3: Done.
Time: 09:52:50.022087    Malloc, Peak: (231650, 240036)     Process 2: Done.
Time: 09:52:51.020856    Malloc, Peak: (231274, 240036)     Process 1: Done.

نخ زنی

Threading جایگزینی برای چند پردازش است که دارای مزایا و معایب است.

Thread ها به طور مستقل برنامه ریزی می شوند و اجرای آنها ممکن است در یک بازه زمانی همپوشانی رخ دهد. با این حال، بر خلاف چند پردازش، نخ ها به طور کامل در یک هسته وجود دارند process، و یک پشته اختصاص داده شده را به اشتراک بگذارید.

رشته های پایتون هستند هم زمان – توالی های متعددی از کد ماشین در بازه های زمانی همپوشانی اجرا می شوند. اما آنها نیستند موازی – اجرا به طور همزمان انجام نمی شود روی چندین هسته فیزیکی

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

برای تقویت ایمنی نخ، CPython پیاده سازی ها از قفل مفسر جهانی (GIL) استفاده می کنند. GIL یک مکانیسم mutex است که از اجرای همزمان چندین رشته جلوگیری می کند. روی اشیاء پایتون به طور موثر، این بدان معناست که در هر زمان معین فقط یک رشته اجرا می شود.

در اینجا نسخه رشته ای مثال چند پردازشی از بخش قبل است. توجه داشته باشید که خیلی کمی تغییر کرده است: multiprocessing.Process جایگزین می شود threading.Thread. همانطور که در خروجی نشان داده شده است، همه چیز در یک واحد اتفاق می افتد process، و ردپای حافظه به طور قابل توجهی کوچکتر است.

from threading import Thread
import os, time, datetime, random, tracemalloc

tracemalloc.start()
children = 4    
maxdelay = 6    

def status():
    return ('Time: ' + 
        str(datetime.datetime.now().time()) +
        '\t Malloc, Peak: ' +
        str(tracemalloc.get_traced_memory()))

def child(num):
    delay = random.randrange(maxdelay)
    print(f"{status()}\t\tProcess {num}, PID: {os.getpid()}, Delay: {delay} seconds...")
    time.sleep(delay)
    print(f"{status()}\t\tProcess {num}: Done.")

if __name__ == '__main__':
    print(f"Parent PID: {os.getpid()}")
    for i in range(children):
        thr = Thread(target=child, args=(i,))
        thr.start()

خروجی:

Parent PID: 19770
Time: 10:44:40.942558    Malloc, Peak: (9150, 9264)     Process 0, PID: 19770, Delay: 3 seconds...
Time: 10:44:40.942937    Malloc, Peak: (13989, 14103)       Process 1, PID: 19770, Delay: 5 seconds...
Time: 10:44:40.943298    Malloc, Peak: (18734, 18848)       Process 2, PID: 19770, Delay: 3 seconds...
Time: 10:44:40.943746    Malloc, Peak: (23959, 24073)       Process 3, PID: 19770, Delay: 2 seconds...
Time: 10:44:42.945896    Malloc, Peak: (26599, 26713)       Process 3: Done.
Time: 10:44:43.945739    Malloc, Peak: (26741, 27223)       Process 0: Done.
Time: 10:44:43.945942    Malloc, Peak: (26851, 27333)       Process 2: Done.
Time: 10:44:45.948107    Malloc, Peak: (24639, 27475)       Process 1: Done.

ناهمزمانی

Asynchrony جایگزینی برای Threading برای نوشتن برنامه های همزمان است. رویدادهای ناهمزمان رخ می دهد روی برنامه های مستقل، “ناهمگام” با یکدیگر، کاملا در یک رشته.

بر خلاف threading، در برنامه‌های ناهمزمان، برنامه‌نویس زمان و چگونگی پیش‌گیری داوطلبانه را کنترل می‌کند و جداسازی و اجتناب از شرایط مسابقه را آسان‌تر می‌کند.

پیشنهاد می‌کنیم بخوانید:  دو دیکشنری پایتون را در یک عبارت ادغام کنید

مقدمه ای بر پایتون 3.7 asyncio مدول

در پایتون 3.7، عملیات ناهمزمان توسط asyncio مدول.

سطح بالا در مقابل سطح پایین asyncio API

asyncio مؤلفه‌ها به APIهای سطح بالا (برای نوشتن برنامه‌ها) و APIهای سطح پایین (برای نوشتن کتابخانه‌ها یا چارچوب‌های مبتنی بر چارچوب) تقسیم می‌شوند. روی asyncio).

هر asyncio برنامه را می توان تنها با استفاده از API های سطح بالا نوشت. اگر چارچوب یا کتابخانه ای نمی نویسید، هرگز نیازی به دست زدن به موارد سطح پایین ندارید.

با این اوصاف، اجازه دهید به APIهای سطح بالا اصلی نگاه کنیم و مفاهیم اصلی را مورد بحث قرار دهیم.

کوروتین ها

به طور کلی، الف روتین (کوتاه برای زیربرنامه تعاونی) تابعی است که برای داوطلبانه چندوظیفه‌ای پیشگیرانه: به‌جای اینکه هسته به‌طور اجباری از آن جلوگیری کند، به‌طور پیشگیرانه به روال‌ها و فرآیندهای دیگر تسلیم می‌شود. اصطلاح “کوروتین” در سال 1958 توسط ملوین کانوی (از شهرت “قانون کانوی”) برای توصیف کدی که به طور فعال نیازهای سایر بخش‌های یک سیستم را تسهیل می‌کند، ابداع شد.

که در asyncio، به این تقدم اختیاری گفته می شود چشم انتظار.

Awaitables، Async و Await

به هر شیئی که می توان منتظر ماند (به طور داوطلبانه توسط یک برنامه پیش دستی) قابل انتظار.

را await کلمه کلیدی اجرای کوروتین فعلی را به حالت تعلیق در می آورد و مورد مشخص شده را به حالت انتظار فرا می خواند.

در پایتون 3.7، سه شیء قابل انتظار هستند coroutine، task، و future.

یک asyncio coroutine هر تابع پایتون است که تعریف آن با پیشوند the باشد async کلمه کلیدی.

async def my_coro():
    pass

یک asyncio task یک شی است که یک coroutine را بسته بندی می کند، روش هایی را برای کنترل اجرای آن ارائه می دهد و وضعیت آن را پرس و جو می کند. ممکن است یک کار با asyncio.create_task()، یا asyncio.gather().

یک asyncio future یک شی سطح پایین است که به عنوان یک مکان نگهدار برای داده هایی که هنوز محاسبه یا واکشی نشده اند عمل می کند. می‌تواند یک ساختار خالی برای پر شدن با داده‌ها در آینده و یک مکانیسم برگشتی ارائه کند که زمانی که داده‌ها آماده هستند فعال می‌شود.

یک کار به جز دو روش موجود در a را به ارث می برد future، بنابراین در پایتون 3.7 شما هرگز نیازی به ایجاد a future به طور مستقیم اعتراض کنید

حلقه های رویداد

که در asyncio، یک حلقه رویداد برنامه ریزی و ارتباط اشیاء قابل انتظار را کنترل می کند. یک حلقه رویداد برای استفاده از awaitable ها لازم است. هر asyncio برنامه حداقل یک حلقه رویداد دارد. ممکن است چندین حلقه رویداد داشته باشید، اما حلقه های رویداد متعدد در پایتون 3.7 به شدت ممنوع است.

ارجاع به شی حلقه در حال اجرا با فراخوانی به دست می آید asyncio.get_running_loop().

خوابیدن

را asyncio.sleep(delay) بلوک های کوروتین برای delay ثانیه برای شبیه سازی مسدود کردن ورودی/خروجی مفید است.

import asyncio

async def main():
    print("Sleep now.")
    await asyncio.sleep(1.5)
    print("OK, wake up!")

asyncio.run(main())

راه اندازی حلقه رویداد اصلی

نقطه ورودی متعارف به یک asyncio برنامه است asyncio.run(main())، جایی که main() یک کوروتین سطح بالا است.

import asyncio

async def my_coro(arg):
    "A coroutine."  
    print(arg)

async def main():
    "The top-level coroutine."
    await my_coro(42)

asyncio.run(main())

صدا زدن asyncio.run() به طور ضمنی یک حلقه رویداد ایجاد و اجرا می کند. شی حلقه دارای روش های مفید بسیاری از جمله loop.time()، که یک شناور نشان دهنده زمان جاری را که توسط ساعت داخلی حلقه اندازه گیری می شود، برمی گرداند.

توجه داشته باشید: asyncio.run() تابع را نمی توان از داخل یک حلقه رویداد موجود فراخوانی کرد. بنابراین، اگر برنامه را در یک محیط نظارتی اجرا می کنید، ممکن است خطاهایی را مشاهده کنید، مانند Anaconda یا Jupyter، که خودش یک حلقه رویداد را اجرا می کند. برنامه های نمونه در این بخش و بخش های زیر باید مستقیماً از خط فرمان با اجرای عبارت اجرا شوند python فایل.

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

برنامه زیر خطوط متن را چاپ می‌کند و بعد از هر خط یک ثانیه تا آخرین خط مسدود می‌شود.

import asyncio

async def my_coro(delay):
    loop = asyncio.get_running_loop()
    end_time = loop.time() + delay
    while True:
        print("Blocking...")
        await asyncio.sleep(1)
        if loop.time() > end_time:
            print("Done.")
            break

async def main():
    await my_coro(3.0)

asyncio.run(main())

خروجی:

Blocking...
Blocking...
Blocking...
Done.

وظایف

وظیفه یک شیء قابل انتظار است که یک کوروتین را می‌پیچد. برای ایجاد و زمان‌بندی فوری یک کار، می‌توانید با شماره زیر تماس بگیرید:

asyncio.create_task(coro(args...))

این یک شیء وظیفه را برمی گرداند. ایجاد یک کار به حلقه می‌گوید: «به پیش بروید و در اسرع وقت این برنامه را اجرا کنید».

اگر شما در انتظار یک کار، اجرای کوروتین فعلی مسدود می شود تا زمانی که آن کار کامل شود.

import asyncio

async def my_coro(n):
    print(f"The answer is {n}.")

async def main():
    
    
    mytask = asyncio.create_task(my_coro(42))
    
    
    
    
    await mytask

asyncio.run(main())

خروجی:

The answer is 42.

Tasks چندین روش مفید برای مدیریت کوروتین پیچیده دارد. قابل ذکر است، می‌توانید با فراخوانی وظیفه، درخواست لغو یک کار را بدهید .cancel() روش. کار برای لغو برنامه ریزی خواهد شد روی چرخه بعدی حلقه رویداد. لغو تضمین نمی شود: کار ممکن است قبل از آن چرخه کامل شود، در این صورت لغو رخ نمی دهد.

گردآوری موارد در انتظار

قابل انتظار می تواند باشد جمع آوری کرد به عنوان یک گروه، با ارائه آنها به عنوان یک آرگومان لیست در برنامه داخلی asyncio.gather(awaitables).

را asyncio.gather() انتظاری را برمی‌گرداند که نشان‌دهنده انتظارات جمع‌آوری‌شده است، و بنابراین باید پیشوند آن باشد await.

اگر هر عنصری از قابل انتظار یک برنامه کاری است، بلافاصله به عنوان یک کار برنامه ریزی می شود.

گردآوری روشی مناسب برای برنامه‌ریزی برنامه‌های متعدد برای اجرای همزمان به عنوان کار است. همچنین وظایف جمع آوری شده را به روش های مفیدی مرتبط می کند:

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

مثال: همگام سازی درخواست های وب با aiohttp

مثال زیر چگونگی این سطح بالا را نشان می دهد asyncio API ها قابل پیاده سازی هستند. نسخه زیر یک نسخه اصلاح شده است که برای Python 3.7 از Scott Robinson’s nifty به روز شده است. asyncio مثال. برنامه او از اهرم aiohttp ماژول برای گرفتن پست های برتر روی Reddit، و خروجی آنها را به console.

مطمئن شوید که آن را دارید aiohttp ماژول قبل از اجرای اسکریپت زیر نصب شده است. شما می توانید ماژول را از طریق زیر دانلود کنید pip دستور:

$ pip install --user aiohttp
import sys  
import asyncio  
import aiohttp  
import json
import datetime

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, numposts):  
    data = await get_json(client, 'https://www.reddit.com/r/' + 
        subreddit + '/top.json؟sort=top&t=day&limit=' +
        str(numposts))

    print(f'\n/r/{subreddit}:')

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

async def main():
    print(datetime.datetime.now().strftime("%A, %B %d, %I:%M %p"))
    print('---------------------------')
    loop = asyncio.get_running_loop()  
    async with aiohttp.ClientSession(loop=loop) as client:
        await asyncio.gather(
            get_reddit_top('python', client, 3),
            get_reddit_top('programming', client, 4),
            get_reddit_top('asyncio', client, 2),
            get_reddit_top('dailyprogrammer', client, 1)
            )

asyncio.run(main())

اگر برنامه را چندین بار اجرا کنید، خواهید دید که ترتیب خروجی ها تغییر می کند. دلیلش این است که درخواست‌های JSON به محض دریافت نمایش داده می‌شوند که وابسته است روی زمان پاسخ سرور و تأخیر شبکه میانی. در یک سیستم لینوکس، می توانید با اجرای اسکریپت با پیشوند (به عنوان مثال) این را در عمل مشاهده کنید. watch -n 5، که خروجی را هر 5 ثانیه تازه می کند:

نمای کلی Async IO در پایتون 3.7

سایر APIهای سطح بالا

امیدواریم این مرور کلی به شما یک پایه محکم از روش، زمان و چرایی استفاده بدهد asyncio. دیگر در سطح بالا asyncio APIهایی که در اینجا پوشش داده نمی شوند، عبارتند از:

  • جریان، مجموعه ای از شبکه های اولیه سطح بالا برای مدیریت رویدادهای TCP ناهمزمان.
  • قفل کردن، رویداد، وضعیت، آنالوگ های ناهمگام از ابتدایی های همگام سازی ارائه شده در نخ زنی مدول.
  • فرآیند فرعی، مجموعه ای از ابزارها برای اجرای زیر فرآیندهای غیر همگام، مانند دستورات پوسته.
  • صف، یک آنالوگ ناهمزمان از صف مدول.
  • استثنا، برای رسیدگی به استثناها در کدهای همگام.

نتیجه

به خاطر داشته باشید که حتی اگر برنامه شما به دلایل عملکردی به ناهمزمانی نیاز نداشته باشد، همچنان می توانید از آن استفاده کنید asyncio اگر نوشتن در پارادایم ناهمزمان را ترجیح می دهید. امیدوارم این مرور کلی به شما درک کاملی از چگونگی، زمان و چرایی شروع استفاده بدهد asyncio.

(برچسب‌ها به ترجمه)# python



منتشر شده در 1403-01-22 19:50:03

امتیاز شما به این مطلب
دیدگاه شما در خصوص مطلب چیست ؟

آدرس ایمیل شما منتشر نخواهد شد.

لطفا دیدگاه خود را با احترام به دیدگاه های دیگران و با توجه به محتوای مطلب درج کنید