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

Please login to bookmark Close

یک Consumer نوشته‌اید که باید منتظر بماند تا در صف چیزی باشد، اما به‌جای انتظار، مدام CPU را می‌سوزاند یا بدتر، آیتمی را که هنوز نرسیده pop می‌کند و برنامه با IndexError می‌ترکد. راه‌حلِ نصفه‌ونیمه معمولاً یک حلقهٔ while با asyncio.sleep است که هم زشت است و هم شرط را به‌درستی هماهنگ نمی‌کند. اینجا دقیقاً جایی است که کلاس Condition در asyncio وارد می‌شود و مشکل را تمیز حل می‌کند.

چرا Event کافی نیست؟

در درس قبل asyncio.Event را دیدیم که فقط یک سیگنال روشن/خاموش می‌فرستد و هیچ اطلاعی از وضعیت داده‌های مشترک ندارد. اما خیلی وقت‌ها سؤال این نیست که «آیا اتفاقی افتاد؟» بلکه این است که «آیا این شرط خاص روی دادهٔ مشترک برقرار شد؟» مثلاً «صف خالی نباشد». کلاس Condition یک Lock را با مکانیزم wait/notify ترکیب می‌کند تا هم دسترسی همزمان را ایمن کند و هم انتظار مبتنی بر شرط را ممکن سازد.

ساخت و ساختار کلاس Condition

با asyncio.Condition() یک شیء می‌سازید که درونش یک asyncio.Lock نهفته است. هر دستکاری روی دادهٔ مشترک باید داخل async with condition انجام شود؛ این بلوک همان Lock را می‌گیرد و آزاد می‌کند. متدهای کلیدی:

  • await condition.wait(): باید داخل async with باشد؛ Lock را موقتاً آزاد می‌کند، منتظر notify می‌ماند و بعد دوباره Lock را می‌گیرد.
  • await condition.wait_for(predicate): تا وقتی تابع شرط True نشده، خودکار wait را تکرار می‌کند.
  • condition.notify(n=1): یک (یا n) Task منتظر را بیدار می‌کند.
  • condition.notify_all(): همهٔ منتظرها را بیدار می‌کند.

مثال واقعی: Producer و Consumer با یک شرط مشخص

Producer آیتم تولید می‌کند و Consumer فقط وقتی باید بیدار شود که لیست مشترک حداقل یک عضو داشته باشد. این شرط را دقیقاً به wait_for می‌سپاریم:

import asyncio
import random

async def producer(condition, items):
    for i in range(5):
        await asyncio.sleep(random.uniform(0.2, 0.6))
        async with condition:                 # take the lock before touching shared data
            items.append(f'item-{i}')
            print(f'producer -> item-{i}')
            condition.notify()                # wake a waiting consumer

async def consumer(condition, items):
    while True:
        async with condition:
            # sleep until there is really something to take
            await condition.wait_for(lambda: len(items) > 0)
            item = items.pop(0)
            print(f'consumer <- {item}')
        if item == 'item-4':
            break

async def main():
    condition = asyncio.Condition()
    items = []
    await asyncio.gather(
        producer(condition, items),
        consumer(condition, items),
    )

asyncio.run(main())

حالا Consumer دیگر هرگز روی لیست خالی pop نمی‌کند، چون wait_for تضمین می‌کند تا وقتی شرط برقرار نشده، بیدار نمی‌شود. اگر هم به اشتباه از notify بی‌مورد بیدار شود، شرط دوباره چک می‌شود و بی‌سروصدا برمی‌گردد به خواب.

دام رایج: notify بیرون از بلوک

رایج‌ترین اشتباه این است که دادهٔ مشترک را تغییر دهید ولی notify را بیرون از همان async with صدا بزنید. دلیلش که مهم است: notify فقط زمانی معنا دارد که Lock در دست شماست؛ اگر بیرون از بلوک صدایش بزنید، هماهنگیِ بیدارسازی از دست می‌رود و ممکن است Task منتظر اصلاً از تغییر باخبر نشود یا با شرطِ ناسازگار بیدار شود. پس همیشه: تغییر داده و notify، هر دو داخل یک بلوک.

کِی از Condition استفاده کنیم؟

اگر فقط یک سیگنال ساده لازم دارید، Event سبک‌تر است. اگر می‌خواهید تعداد Taskهای همزمان را محدود کنید، سراغ Semaphore بروید. اما وقتی چند Task باید بر اساس وضعیت یک دادهٔ مشترک با هم هماهنگ شوند، Condition انتخاب درست است.

جمع‌بندی

  • Condition یک Lock را با wait/notify ترکیب می‌کند تا انتظارِ مبتنی بر شرط ممکن شود.
  • از wait_for(predicate) استفاده کنید تا Task فقط با برقراری شرط واقعی بیدار شود و از bug مثل pop روی لیست خالی جلوگیری شود.
  • تغییر دادهٔ مشترک و notify را همیشه داخل یک بلوک async with نگه دارید.
  • برای سیگنال ساده Event و برای محدودکردن همزمانی Semaphore مناسب‌ترند؛ Condition برای هماهنگی بر پایهٔ شرط است.
Please login to bookmark Close
پیشرفت شما در «دوره آموزش کانکارنسی در پایتون» (62%)
نظرات

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

62%
پیشرفت

سرفصل دوره

فهرست مطالب

سرفصل دوره

تمرین

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

پاسخ تمرین ها

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

اشتراک گذاری

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

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

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

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

تنظیمات

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