مقدمه
فرض کنید قصد دارید چند درخواست شبکه را بهصورت همزمان ارسال کنید، اما سرور مقصد یا منابع سیستم شما اجازهٔ بیش از تعداد معینی اتصال همزمان را نمیدهد. در چنین شرایطی، باید بتوانید تعداد Taskهایی را که همزمان در حال اجرا هستند محدود کنید. کلاس Semaphore در ماژول asyncio دقیقاً همین کار را انجام میدهد.
Semaphore چیست؟
Semaphore یک شمارندهٔ داخلی دارد که با یک مقدار اولیه ساخته میشود. هر بار که یک Task آن را acquire (تصاحب) میکند، این شمارنده یک واحد کم میشود، و هر بار که release (آزاد) میشود، یک واحد زیاد میشود. اگر شمارنده به صفر برسد، هر Task جدیدی که بخواهد آن را تصاحب کند، باید منتظر بماند تا یکی از Taskهای قبلی آن را آزاد کند.
import asyncio
async def main():
semaphore = asyncio.Semaphore(2)
print(f'مقدار اولیه: {semaphore._value}')
asyncio.run(main())استفاده با async with
راحتترین روش استفاده از Semaphore، بهکار بردن آن بهصورت یک Context Manager با async with است. در این حالت، در ابتدای بلوک acquire و در پایان بلوک release بهصورت خودکار انجام میشود، حتی اگر داخل بلوک خطایی رخ دهد.
import asyncio
async def fetch_data(semaphore, name, delay):
async with semaphore:
print(f'{name}: شروع شد')
await asyncio.sleep(delay)
print(f'{name}: پایان یافت')
async def main():
semaphore = asyncio.Semaphore(2)
tasks = [
asyncio.create_task(fetch_data(semaphore, f'Task {i}', 2))
for i in range(5)
]
await asyncio.gather(*tasks)
asyncio.run(main())در این مثال، با اینکه ۵ Task ساخته شده، اما چون مقدار Semaphore برابر با ۲ است، در هر لحظه حداکثر ۲ Task بهصورت همزمان اجرا میشوند و بقیه منتظر میمانند تا یکی از آن دو، semaphore را آزاد کند.
بهجای async with میتوان از روش دستی هم استفاده کرد، اما در این حالت باید حتماً از try/finally استفاده کنید تا در صورت بروز خطا هم release فراموش نشود:
await semaphore.acquire()
try:
pass
finally:
semaphore.release()تفاوت Semaphore و BoundedSemaphore
کلاس asyncio.BoundedSemaphore دقیقاً مثل Semaphore عمل میکند، با یک تفاوت: اگر تعداد release بیشتر از تعداد acquire باشد، خطای ValueError ایجاد میکند. این ویژگی برای پیدا کردن باگهایی که در آنها release بهاشتباه چند بار صدا زده میشود بسیار مفید است.
جمعبندی
کلاس Semaphore ابزاری ساده و کاربردی برای محدود کردن تعداد Taskهای همزمانِ فعال است؛ مثلاً برای کنترل تعداد درخواستهای همزمان به یک API یا پایگاهداده. در درس بعدی با کلاس Event در asyncio آشنا میشویم که به Taskها امکان میدهد منتظر یک رویداد مشخص بمانند.