از طریق منوی جستجو مطلب مورد نظر خود در وبلاگ را به سرعت پیدا کنید
نمای کلی Async IO در پایتون 3.7
سرفصلهای مطلب
پایتون 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 فایل.
برنامه زیر خطوط متن را چاپ میکند و بعد از هر خط یک ثانیه تا آخرین خط مسدود میشود.
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 ثانیه تازه می کند:
سایر APIهای سطح بالا
امیدواریم این مرور کلی به شما یک پایه محکم از روش، زمان و چرایی استفاده بدهد asyncio
. دیگر در سطح بالا asyncio
APIهایی که در اینجا پوشش داده نمی شوند، عبارتند از:
- جریان، مجموعه ای از شبکه های اولیه سطح بالا برای مدیریت رویدادهای TCP ناهمزمان.
- قفل کردن، رویداد، وضعیت، آنالوگ های ناهمگام از ابتدایی های همگام سازی ارائه شده در نخ زنی مدول.
- فرآیند فرعی، مجموعه ای از ابزارها برای اجرای زیر فرآیندهای غیر همگام، مانند دستورات پوسته.
- صف، یک آنالوگ ناهمزمان از صف مدول.
- استثنا، برای رسیدگی به استثناها در کدهای همگام.
نتیجه
به خاطر داشته باشید که حتی اگر برنامه شما به دلایل عملکردی به ناهمزمانی نیاز نداشته باشد، همچنان می توانید از آن استفاده کنید asyncio
اگر نوشتن در پارادایم ناهمزمان را ترجیح می دهید. امیدوارم این مرور کلی به شما درک کاملی از چگونگی، زمان و چرایی شروع استفاده بدهد asyncio
.
(برچسبها به ترجمه)# python
منتشر شده در 1403-01-22 19:50:03