آموزش ماژول asyncio – کلاس Semaphore

Please login to bookmark Close

فرض کنید می‌خواهید ۵۰۰ تصویر را از یک API دانلود کنید و برای سرعت، همه را با asyncio.gather یکجا اجرا می‌کنید. چند ثانیه بعد، سرور خطای 429 Too Many Requests می‌دهد یا اتصال‌هایتان تایم‌اوت می‌شوند. چرا؟ چون ۵۰۰ اتصال هم‌زمان را هیچ سروری دوست ندارد. اینجا به ابزاری نیاز دارید که تعداد Taskهای هم‌زمان را محدود کند؛ asyncio Semaphore دقیقاً همین کار را می‌کند.

asyncio Semaphore چگونه کار می‌کند؟

Semaphore یک شمارندهٔ داخلی دارد که با یک مقدار اولیه ساخته می‌شود. هر بار که یک Task آن را acquire می‌کند، شمارنده یک واحد کم می‌شود، و با release یک واحد زیاد می‌شود. اگر شمارنده به صفر برسد، هر Task جدیدی که بخواهد آن را تصاحب کند معلق می‌ماند تا یکی از Taskهای قبلی آن را آزاد کند. در واقع Semaphore می‌گوید: «حداکثر N نفر هم‌زمان داخل اتاق، بقیه دم در صبر کنید.»

راه درست: async with

راحت‌ترین و امن‌ترین روش، استفاده از Semaphore به‌صورت یک Context Manager با async with است. در این حالت، ابتدای بلوک acquire و پایان آن release به‌صورت خودکار انجام می‌شود، حتی اگر داخل بلوک خطایی رخ دهد. بیایید همان مثال دانلود را با سقف دو درخواست هم‌زمان پیاده کنیم:

import asyncio

async def fetch(semaphore, name, delay):
    async with semaphore:              # acquire on enter, release on exit
        print(f'{name}: شروع')
        await asyncio.sleep(delay)     # pretend this is a network call
        print(f'{name}: پایان')

async def main():
    semaphore = asyncio.Semaphore(2)   # at most 2 downloads at once
    tasks = [
        asyncio.create_task(fetch(semaphore, f'دانلود {i}', 2))
        for i in range(5)
    ]
    await asyncio.gather(*tasks)

asyncio.run(main())

با اینکه ۵ Task ساخته شده، چون مقدار Semaphore برابر ۲ است، در هر لحظه حداکثر ۲ دانلود هم‌زمان اجرا می‌شوند و بقیه صبر می‌کنند تا یکی از آن دو، semaphore را آزاد کند. همین الگو دقیقاً راه‌حل مشکل rate-limiting در APIها است.

روش دستی و دام رایج آن

می‌توان acquire و release را دستی هم صدا زد، اما اینجا یک دام جدی وجود دارد: اگر داخل بلوک خطایی رخ دهد و شما release را فراموش کنید، آن واحد از شمارنده برای همیشه قفل می‌شود و کم‌کم کل برنامه گیر می‌کند. به همین دلیل، اگر مجبور به روش دستی شدید، حتماً از try/finally استفاده کنید:

await semaphore.acquire()
try:
    ...                     # your work here
finally:
    semaphore.release()     # ALWAYS runs, even on error

توجه کنید که نسخهٔ async with دقیقاً همین الگوی try/finally را در دل خود دارد؛ پس به‌جز موارد خاص، همیشه نسخهٔ Context Manager را ترجیح دهید.

Semaphore یا BoundedSemaphore؟

کلاس asyncio.BoundedSemaphore دقیقاً مثل Semaphore عمل می‌کند، با یک تفاوت: اگر تعداد release بیشتر از acquire شود، خطای ValueError می‌دهد. این دقیقاً همان باگی است که در کدهای بزرگ به سختی پیدا می‌شود: یک release اضافه که سقف هم‌زمانی را بی‌سروصدا بالا می‌برد. بهتر است در کد حساس، BoundedSemaphore را ترجیح دهید تا چنین باگی زود خودش را نشان دهد.

جمع‌بندی

  • Semaphore تعداد Taskهای هم‌زمان فعال را محدود می‌کند؛ ایده‌آل برای rate-limiting درخواست‌ها به API یا پایگاه‌داده.
  • همیشه نسخهٔ async with را ترجیح دهید تا release فراموش نشود.
  • در روش دستی، فراموش کردن release در مسیر خطا برنامه را قفل می‌کند؛ try/finally اجباری است.
  • برای گرفتن باگ‌های release اضافی، از BoundedSemaphore استفاده کنید.
Please login to bookmark Close
پیشرفت شما در «دوره آموزش کانکارنسی در پایتون» (59%)
نظرات

دیدگاهتان را بنویسید

59%
پیشرفت

سرفصل دوره

فهرست مطالب

سرفصل دوره

تمرین

این قسمت تمرین ندارد!

پاسخ تمرین ها

هنوز برای تمرین‌های این قسمت پاسخی ثبت نشده است!

اشتراک گذاری

چرا بهتره از فیلترشکن استفاده کنید؟

من همه ویدئو ها و پادکست های کُدباز رو توی یوتیوب و ساندکلود و پلتفرم هایی آپلود می‌کنم که اغلب فیلتر هستند.

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

توجه داشته باشید که برای خرید از فروشگاه بهتره فیلتر شکن رو خاموش کنید.

تنظیمات

انتخاب زبان
تغییر تم