برای سالها، قفل مفسر جهانی (GIL) در پایتون به عنوان دلیل اصلی برای انتخاب چندپردازشی به جای چندرشتهای در ساخت برنامههای همزمان ذکر میشد. با این حال، معرفی asyncio در پایتون ۳.۴ نحوه مدیریت وظایف وابسته به ورودی/خروجی (I/O) را متحول کرد. با بهرهگیری از چندوظیفگی مشارکتی، توسعهدهندگان پایتون اکنون میتوانند با حداقل سربار حافظه، هزاران اتصال همزمان را مدیریت کنند. در این پست، مفاهیم اصلی asyncio را بررسی کرده و آن را از رشتهبندی سنتی متمایز میسازیم، و مثالهای کد عملی ارائه میدهیم تا به شما در نوشتن کد پایتون کارآمد و غیرمسدودکننده کمک کنیم.
درک حلقه رویداد و کوروتینها
در قلب asyncio، حلقه رویداد قرار دارد. برخلاف مدل گروه رشتهای که به سیستمعامل برای تغییر زمینه بین رشتهها تکیه دارد، حلقه رویداد در یک رشته واحد اجرا میشود و صریحاً بین وظایف جابجا میشود. این رویکرد سربار تغییر زمینه و رقابت برای قفلها را حذف میکند، به شرطی که کد شما قرارداد همزمانی را رعایت کند.
async def تعریف میشود و تابعی است که میتواند اجرای خود را متوقف کند (با استفاده از await) و بعداً از سر بگیرد، که به حلقه رویداد اجازه میدهد در این فاصله وظایف دیگر را اجرا کند. این موضوع برای عملیات ورودی/خروجی مانند درخواستهای شبکه یا خواندن فایل حیاتی است، جایی که برنامه بیشتر زمان خود را منتظر منابع خارجی میگذراند.
تفاوت بین یک تابع سینکرون و یک کوروتین را در نظر بگیرید:
import asyncio
# یک تابع سینکرون استاندارد
def sync_fetch_data():
print("Starting sync fetch...")
# شبیهسازی ورودی/خروجی مسدودکننده
time.sleep(2)
print("Sync fetch complete.")
return "Data"
# یک کوروتین ناهمگام
async def async_fetch_data():
print("Starting async fetch...")
# شبیهسازی انتظار ورودی/خروجی غیرمسدودکننده
await asyncio.sleep(2)
print("Async fetch complete.")
return "Data"
در مثال بالا، sync_fetch_data کل رشته را برای دو ثانیه مسدود میکند. اگر شما این تابع را در داخل یک حلقه رویداد فراخوانی کنید، هیچ وظیفه دیگری در آن بازه زمانی نمیتواند اجرا شود. در مقابل، async_fetch_data کنترل را در طول دستور await به حلقه رویداد بازمیگرداند و اجازه میدهد کوروتینهای دیگر به صورت همزمان اجرا شوند.
زمانبندی وظایف با gather
یکی از قدرتمندترین ویژگیهای asyncio توانایی اجرای چندین کوروتین به صورت همزمان است. اگرچه میتوانید کوروتینها را به صورت دستی با استفاده از asyncio.create_task() اجرا کنید، اما تابع asyncio.gather() یک رابط کاربری تمیزتر برای جمعآوری نتایج از چندین عملیات همزمان ارائه میدهد.
بیایید به یک مثال عملی نگاه کنیم که در آن شبیهسازی دریافت داده از دو نقطه پایانی API مختلف را به صورت همزمان انجام میدهیم.
import asyncio
import time
async def fetch_site(url):
print(f"Fetching {url}...")
# شبیهسازی تأخیر شبکه
await asyncio.sleep(2)
print(f"Finished fetching {url}")
return f"Content from {url}"
async def main():
start_time = time.time()
# اجرای هر دو کوروتین به صورت همزمان
results = await asyncio.gather(
fetch_site('https://example.com'),
fetch_site('https://python.org')
)
end_time = time.time()
print(f"Total time: {end_time - start_time:.2f} seconds")
print(f"Results: {results}")
# اجرای حلقه رویداد
if __name__ == "__main__":
asyncio.run(main())
بدون gather، اگر ما این توابع را به صورت متوالی فراخوانی میکردیم، زمان اجرای کل تقریباً چهار ثانیه میشد (2 ثانیه + 2 ثانیه). با gather، حلقه رویداد هر دو وظیفه را زمانبندی کرده و منتظر تکمیل هر دو میماند. از آنجا که آنها به صورت همزمان اجرا میشوند، زمان کل نزدیک به دو ثانیه است. این مقیاسپذیری خطی در صرفهجویی در زمان، دلیل اثربخشی بالای برنامهنویسی ناهمگام برای بارهای کاری وابسته به ورودی/خروجی است.
اشتباهات رایج و بهترین شیوهها
در حالی که asyncio مزایای عملکردی قابل توجهی ارائه میدهد، اما راه حل جادویی نیست. یکی از رایجترین اشتباهاتی که توسعهدهندگان مرتکب میشوند، فراخوانی توابع ورودی/خروجی مسدودکننده (مانند time.sleep() یا کوئریهای پایگاه داده سینکرون) در داخل یک کوروتین است. انجام این کار کل حلقه رویداد را مسدود میکند و به طور مؤثر کد ناهمگام شما را به کد سینکرون تبدیل کرده و تمام مزایای عملکردی را از بین میبرد.
برای جلوگیری از این موضوع، همیشه اطمینان حاصل کنید که کتابخانههایی که استفاده میکنید از اکوسیستم ناهمگام پشتیبانی میکنند. برای درخواستهای HTTP، کتابخانههایی مانند aiohttp یا httpx نسبت به requests ترجیح داده میشوند. برای پایگاههای داده، در نظر بگیرید که از asyncpg برای PostgreSQL یا motor برای MongoDB استفاده کنید. همیشه مستندات را بررسی کنید تا مطمئن شوید کتابخانهای که با آن ادغام میکنید واقعاً با ناهمگام سازگار است.
نتیجهگیری
تسلط بر asyncio برای هر توسعهدهنده پایتون که به دنبال ساخت برنامههای مقیاسپذیر و با عملکرد بالا است، به ویژه آنهایی که شامل ورودی/خروجی شبکه سنگین هستند، ضروری است. با درک حلقه رویداد، استفاده صحیح از کوروتینها و بهرهگیری از ابزارهایی مانند asyncio.gather، میتوانید کدی بنویسید که هم کارآمد و هم خوانا باشد. به یاد داشته باشید که همیشه از مسدود کردن حلقه رویداد خودداری کنید و برای بهترین نتایج به کتابخانههای بومی ناهمگام پایبند باشید. با ادامه پذیرش استانداردهای ناهمگام در اکوسیستم پایتون، مهارتهای بحث شده در اینجا در مهندسی نرمافزار مدرن به طور فزایندهای ارزشمند خواهند شد.