اولیه‌های همگام‌سازی

کد منبع: Lib/asyncio/locks.py


اولیه‌های همگام‌سازی asyncio به‌گونه‌ای طراحی شده‌اند که شبیه به همتاهای خود در ماژول threading باشند، با دو نکته‌ی مهم:

  • اولیه‌های asyncio ایمن از نظر نخ (thread-safe) نیستند، بنابراین نباید برای همگام‌سازی نخ‌های سیستم‌عامل از آن‌ها استفاده شود (برای این کار از threading استفاده کنید)؛

  • متدهای این اولیه‌های همگام‌سازی آرگومان timeout را نمی‌پذیرند؛ برای انجام عملیات با مهلت‌های زمانی از تابع asyncio.wait_for() استفاده کنید.

asyncio دارای اولیه‌های پایه‌ی همگام‌سازی زیر است:


قفل

class asyncio.Lock

یک قفل متقابل (mutex lock) برای وظایف asyncio پیاده‌سازی می‌کند. ایمن از نظر نخ (thread-safe) نیست.

می‌توان از یک قفل asyncio برای تضمین دسترسی انحصاری به یک منبع مشترک استفاده کرد.

روش ترجیحی برای استفاده از Lock، دستور async with است:

lock = asyncio.Lock()

# ... later
async with lock:
    # access shared state

که معادل زیر است:

lock = asyncio.Lock()

# ... later
await lock.acquire()
try:
    # access shared state
finally:
    lock.release()

تغییر یافته در نسخه‌ی 3.10: پارامتر loop حذف شد.

async acquire()

قفل را کسب کنید.

این متد منتظر می‌ماند تا قفل باز شود، آن را قفل می‌کند و True را برمی‌گرداند.

هنگامی که بیش از یک هم‌روال در acquire() مسدود شده باشد و منتظر باز شدن قفل باشد، در نهایت تنها یک هم‌روال ادامه پیدا می‌کند.

کسب قفل منصفانه است: هم‌روالی که ادامه می‌دهد، نخستین هم‌روالی خواهد بود که منتظر قفل شده است.

release()

قفل را آزاد کنید.

هنگامی که قفل قفل‌شده است، آن را به باز بازنشانی کنید و بازگردید.

اگر قفل باز باشد، یک RuntimeError پرتاب می‌شود.

locked()

اگر قفل قفل‌شده باشد، True را برمی‌گرداند.

رویداد

class asyncio.Event

یک شیء رویداد. نخ‌ایمن نیست.

می‌توان از یک رویداد asyncio برای آگاه‌سازی چندین وظیفه در asyncio از وقوع رویدادی استفاده کرد.

یک شیء Event یک پرچم داخلی را مدیریت می‌کند که می‌توان آن را با متد set() روی true تنظیم کرد و با متد clear() به false بازنشانی کرد. متد wait() تا زمانی که پرچم روی true تنظیم شود، مسدود می‌شود. پرچم در ابتدا روی false تنظیم شده است.

تغییر یافته در نسخه‌ی 3.10: پارامتر loop حذف شد.

مثال:

async def waiter(event):
    print('waiting for it ...')
    await event.wait()
    print('... got it!')

async def main():
    # Create an Event object.
    event = asyncio.Event()

    # Spawn a Task to wait until 'event' is set.
    waiter_task = asyncio.create_task(waiter(event))

    # Sleep for 1 second and set the event.
    await asyncio.sleep(1)
    event.set()

    # Wait until the waiter task is finished.
    await waiter_task

asyncio.run(main())
async wait()

صبر کنید تا رویداد تنظیم شود.

اگر رویداد تنظیم‌شده باشد، بلافاصله True را برمی‌گرداند. در غیر این صورت، تا زمانی که وظیفه دیگری set() را فراخوانی کند، مسدود می‌شود.

set()

رویداد را تنظیم کنید.

همه‌ی وظایفی که در انتظار تنظیم شدن رویداد هستند، بی‌درنگ بیدار خواهند شد.

clear()

رویداد را پاک (لغو تنظیم) می‌کند.

وظایف بعدی که wait() را await می‌کنند، اکنون تا زمانی که متد set() دوباره فراخوانی شود، مسدود خواهند شد.

is_set()

اگر رویداد تنظیم‌شده باشد، True را برمی‌گرداند.

شرایط

class asyncio.Condition(lock=None)

یک شیء Condition. ایمن از نظر نخ نیست.

یک وظیفه می‌تواند از یک سازوکار شرطی اولیه در asyncio استفاده کند تا برای رخ دادن رویدادی منتظر بماند و سپس دسترسی انحصاری به یک منبع مشترک را به‌دست آورد.

در اصل، یک شیء Condition، کارکرد Event و Lock را ترکیب می‌کند. می‌توان چندین شیء Condition داشت که یک Lock را به اشتراک می‌گذارند؛ این امکان، هماهنگ‌سازی دسترسی انحصاری به یک منبع مشترک را بین وظایف مختلفی که به وضعیت‌های خاصی از آن منبع مشترک علاقه‌مند هستند، ممکن می‌سازد.

آرگومان اختیاری lock باید یک شیء Lock یا None باشد. در حالت دوم، یک شیء Lock جدید به‌صورت خودکار ایجاد می‌شود.

تغییر یافته در نسخه‌ی 3.10: پارامتر loop حذف شد.

روش ترجیح‌داده‌شده برای استفاده از Condition، دستور async with است:

cond = asyncio.Condition()

# ... later
async with cond:
    await cond.wait()

که معادل زیر است:

cond = asyncio.Condition()

# ... later
await cond.acquire()
try:
    await cond.wait()
finally:
    cond.release()
async acquire()

قفل زیربنایی را تصاحب کنید.

این متد تا زمانی که قفل زیربنایی آزاد شود صبر می‌کند، آن را در حالت قفل‌شده قرار می‌دهد و True را برمی‌گرداند.

notify(n=1)

بیدار کردن n وظیفه در انتظار این شرط (به‌طور پیش‌فرض ۱). اگر کمتر از n وظیفه در انتظار باشند، همه‌ی آن‌ها بیدار می‌شوند.

قفل باید پیش از فراخوانی این متد کسب شود و اندکی پس از آن آزاد شود. اگر با یک قفل باز فراخوانی شود، خطای RuntimeError پرتاب می‌شود.

locked()

اگر قفل زیربنایی کسب شده باشد، True برمی‌گرداند.

notify_all()

تمام وظایف در انتظار این شرط را بیدار کنید.

این متد مانند notify() عمل می‌کند، اما تمام وظایف در انتظار را بیدار می‌کند.

قفل باید پیش از فراخوانی این متد کسب شود و اندکی پس از آن آزاد شود. اگر با یک قفل باز فراخوانی شود، خطای RuntimeError پرتاب می‌شود.

release()

قفل زیربنایی را آزاد کنید.

هنگامی که روی یک قفل باز فراخوانی شود، یک RuntimeError پرتاب می‌شود.

async wait()

تا زمانی که به شما اطلاع داده شود، صبر کنید.

اگر در زمان فراخوانی این متد، وظیفه فراخوانی‌کننده قفل را به دست نیاورده باشد، یک RuntimeError پرتاب می‌شود.

این متد قفل زیربنایی را آزاد می‌کند و سپس تا زمانی که با فراخوانی notify() یا notify_all() بیدار شود، مسدود می‌شود. پس از بیدار شدن، Condition قفل خود را دوباره به دست می‌آورد و این متد True را برمی‌گرداند.

توجه داشته باشید که یک وظیفه ممکن است به‌صورت کاذب از این فراخوانی بازگردد، به همین دلیل فراخواننده باید همیشه وضعیت را دوباره بررسی کند و برای فراخوانی دوباره‌ی wait() آماده باشد. به همین دلیل، ممکن است ترجیح دهید به‌جای آن از wait_for() استفاده کنید.

async wait_for(predicate)

صبر کنید تا یک محمول (predicate) به مقدار درست برسد.

محمول باید یک شیء فراخوانی‌پذیر باشد که نتیجه‌ی آن به‌عنوان یک مقدار بولی تفسیر می‌شود. این متد به‌طور مکرر wait() را فراخوانی می‌کند، تا زمانی که محمول به‌مقدار true ارزیابی شود. مقدار نهایی همان مقدار بازگشتی است.

سمافور

class asyncio.Semaphore(value=1)

یک شیء Semaphore. نخ‌ایمن نیست.

سمافور یک شمارنده‌ی داخلی را مدیریت می‌کند که با هر فراخوانی acquire() کاهش می‌یابد و با هر فراخوانی release() افزایش می‌یابد. شمارنده هرگز نمی‌تواند زیر صفر برود؛ هنگامی که acquire() ببیند که شمارنده صفر است، مسدود می‌شود و منتظر می‌ماند تا وظیفهی release() را فراخوانی کند.

آرگومان اختیاری value مقدار اولیه شمارنده داخلی را مشخص می‌کند (به‌طور پیش‌فرض 1). اگر مقدار داده‌شده کمتر از 0 باشد، یک ValueError پرتاب می‌شود.

تغییر یافته در نسخه‌ی 3.10: پارامتر loop حذف شد.

روش ترجیحی برای استفاده از Semaphore، استفاده از یک دستور async with است:

sem = asyncio.Semaphore(10)

# ... later
async with sem:
    # work with shared resource

که معادل زیر است:

sem = asyncio.Semaphore(10)

# ... later
await sem.acquire()
try:
    # work with shared resource
finally:
    sem.release()
async acquire()

یک سمافور را تصاحب کنید.

اگر شمارنده‌ی داخلی بزرگ‌تر از صفر باشد، آن را یک واحد کاهش دهید و بلافاصله True را برگردانید. اگر صفر باشد، منتظر بمانید تا release() فراخوانی شود و True را برگردانید.

locked()

اگر سمافور نتواند بلافاصله کسب شود، True را برمی‌گرداند.

release()

یک سمافور را آزاد می‌کند و شمارنده‌ی داخلی را ۱ واحد افزایش می‌دهد. می‌تواند وظیفه‌ای را که در انتظار به‌دست آوردن سمافور است بیدار کند.

برخلاف BoundedSemaphore، Semaphore اجازه می‌دهد فراخوانی‌های release() بیشتری نسبت به فراخوانی‌های acquire() انجام شود.

BoundedSemaphore

class asyncio.BoundedSemaphore(value=1)

یک شیء سمافور کران‌دار. نخ‌ایمن نیست.

سمافور محدود (Bounded Semaphore) نسخه‌ای از Semaphore است که در release()، اگر شمارنده داخلی را به بالاتر از value اولیه افزایش دهد، ValueError پرتاب می‌کند.

تغییر یافته در نسخه‌ی 3.10: پارامتر loop حذف شد.

سد

class asyncio.Barrier(parties)

یک شیء سد (barrier). نخ‌ایمن نیست.

یک سد، یک ساختار اولیه همگام‌سازی ساده است که امکان مسدود شدن تا زمانی که تعداد parties وظیفه منتظر آن باشند را فراهم می‌کند. وظایف می‌توانند با متد wait() منتظر بمانند و تا زمانی که تعداد مشخص‌شده‌ای از وظایف در نهایت با متد wait() منتظر شوند، مسدود می‌مانند. در آن نقطه، همه وظایف منتظر به‌طور همزمان از حالت مسدود خارج می‌شوند.

async with می‌تواند به‌عنوان جایگزینی برای await روی wait() استفاده شود.

این سد را می‌توان هر تعداد باری دوباره استفاده کرد.

مثال:

async def example_barrier():
   # barrier with 3 parties
   b = asyncio.Barrier(3)

   # create 2 new waiting tasks
   asyncio.create_task(b.wait())
   asyncio.create_task(b.wait())

   await asyncio.sleep(0)
   print(b)

   # The third .wait() call passes the barrier
   await b.wait()
   print(b)
   print("barrier passed")

   await asyncio.sleep(0)
   print(b)

asyncio.run(example_barrier())

نتیجه‌ی این مثال:

<asyncio.locks.Barrier object at 0x... [filling, waiters:2/3]>
<asyncio.locks.Barrier object at 0x... [draining, waiters:0/3]>
barrier passed
<asyncio.locks.Barrier object at 0x... [filling, waiters:0/3]>

اضافه شده در نسخه‌ی 3.11.

async wait()

از سد عبور کنید. هنگامی که همه وظایف شرکت‌کننده در سد این تابع را فراخوانی کرده باشند، همه آن‌ها به‌طور همزمان رفع انسداد می‌شوند.

هنگامی که یک وظیفه‌ی در انتظار یا مسدودشده در سد لغو شود، این وظیفه از سد خارج می‌شود و سد در همان وضعیت باقی می‌ماند. اگر وضعیت سد «در حال پر شدن» باشد، تعداد وظایف در انتظار به میزان ۱ کاهش می‌یابد.

مقدار بازگشتی یک عدد صحیح در بازه‌ی ۰ تا parties-1 است و برای هر وظیفه متفاوت است. می‌توان از این مقدار برای انتخاب یک وظیفه جهت انجام برخی عملیات نگهداری خاص استفاده کرد، برای مثال:

...
async with barrier as position:
   if position == 0:
      # Only one task prints this
      print('End of *draining phase*')

این متد ممکن است در صورتی که سد در حین انتظار یک وظیفه شکسته شود یا بازنشانی شود، استثنای BrokenBarrierError را پرتاب کند. ممکن است در صورت لغو شدن یک وظیفه، استثنای CancelledError را پرتاب کند.

async reset()

سد را به حالت پیش‌فرض و خالی بازمی‌گرداند. هر وظیفهی که در انتظار آن باشد، استثنای BrokenBarrierError را دریافت خواهد کرد.

اگر یک سد (barrier) شکسته شود، ممکن است بهتر باشد آن را به حال خود رها کنید و یک سد جدید ایجاد کنید.

async abort()

سد (Barrier) را در وضعیت شکسته قرار دهید. این کار باعث می‌شود هر فراخوانی فعال یا آینده به wait() با BrokenBarrierError ناموفق شود. برای مثال، اگر یکی از وظایف نیاز به لغو شدن داشته باشد، از این استفاده کنید تا از وظایفی که به‌طور بی‌پایان در انتظار می‌مانند جلوگیری شود.

parties

تعداد وظایف لازم برای عبور از سد.

n_waiting

تعداد وظایفی که در حال حاضر در حین پر شدن سد (barrier) منتظر هستند.

broken

یک بولی که اگر سد در وضعیت شکسته باشد، True است.

exception asyncio.BrokenBarrierError

این استثنا، که زیرکلاسی از RuntimeError است، زمانی پرتاب می‌شود که شیء Barrier بازنشانی یا شکسته شود.


تغییر یافته در نسخه‌ی 3.9: کسب قفل با استفاده از await lock یا yield from lock و/یا دستور with (with await lock، with (yield from lock)) حذف شده است. به جای آن از async with lock استفاده کنید.