Coroutine در پایتون | روش پیاده سازی، مدیریت و رفع مشکلات رایج

Coroutine در پایتون | روش پیاده سازی، مدیریت و رفع مشکلات رایج

نوشته شده توسط: تیم فنی نیک آموز
تاریخ انتشار: ۰۲ مرداد ۱۴۰۳
آخرین بروزرسانی: 02 مرداد 1403
زمان مطالعه: 9 دقیقه
۵
(۱)

Coroutine در پایتون یک قابلیت کارآمد است که برنامه‌نویسی غیرهمزمان را ممکن می‌کند. این تکنیک امکان اجرای چندین کار را در یک رشته واحد فراهم می‌کند. همچنین بخشی از قابلیت‌های غیرهمزمانی ورودی / خروجی (Asynchronous I/O) گسترده‌تر است که توسط پایتون و با کمک کتابخانه asyncio ارائه می‌شود. 

این ویژگی همراه‌با مزایا و قابلیت‌های متعددی ارائه شده است که در این مقاله به آن‌ها می‌پردازیم. عنوان‌هایی همچون تاریخچه و تکامل Coroutine، تفاوت آن با Threading، نحوه تعریف، استفاده از Coroutine و… مباحثی هستند که در ادامه خواهید خواند.

مسیر آموزش پایتون نیک آموز

معرفی Coroutine در پایتون

Coroutine در پایتون تکنیکی برای نوشتن کدها به‌صورت غیرهمزمان (Asynchronous) است. توسط این تابع می‌توانید در یک لحظه واحد، چندین تابع و فرآیند را اجرا کنید. درنتیجه، مجبور به انتظار برای یک ورودی یا خروجی – مانند کارت شبکه یا اجرای تابع قبلی – نخواهید بود. در این تکنیک، برخی از توابع اجرای خود را به حالت تعلیق درمی‌آورند تا در زمان نیاز، دقیقاً از جایی که متوقف شده‌اند، اجرا شوند. Coroutine به سایر کوروتین‌ها اجازه اجرای همزمان را می‌دهد.

تاریخچه و تکامل Coroutine در پایتون 

در سال ۱۹۵۸، یک دانشمند کامپیوتر به نام ملوین کانوی (Melvin Conway) اصطلاح Coroutine را ابداع کرد. او این ترکیب‌واژه‌ای را هنگامی که درحال ساخت یک برنامه اسمبلی بود، به‌کار برد. سپس در سال ۱۹۶۳، اولین توصیف منتشرشده درباره Coroutine در دسترس عموم قرار گرفت.

Coroutineها به‌عنوان یک ویژگی بومی در پایتون ۳.۵، با PEP 492 عرضه و به این زبان برنامه‌نویسی اضافه شدند. 

امروزه این قابلیت علاوه‌‌بر پشتیبانی توسط پایتون، در برخی از انواع زبان های برنامه نویسی سطح بالا هم پشتیبانی می‌شود. می‌توانید نام برخی از این زبان‌ها را در لیست زیر مشاهده کنید:

درحال‌حاضر چهار نسخه‌ از پایتون، Coroutine را پشتیبانی می‌کنند که در ادامه آن‌ها را لیست کرده‌ایم:

  • ۲.۵
  • ۳.۳
  • ۳.۴
  • ۳.۵

تفاوت Coroutine و Threading 

ماژول Threading به شما امکان می‌دهد رشته‌هایی را در برنامه‌های پایتون ایجاد و مدیریت کنید. این ماژول تسک‌ها یا درخواست‌ها را به‌صورت همزمان روی سرورها یا برنامه‌های شما اجرا می‌کند. Coroutine در پایتون روش جدیدتری نسبت‌به Threading است؛ اما هرکدام مزایا و معایب خود را دارند که در ادامه آن‌ها را نام خواهیم برد.

مزایای Threading

  • این ماژول برنامه‌های چندرشته‌ای را روی سیستم‌های کامپیوتری مجهز به چندین CPU، با سرعت بالایی اجرا می‌کند.
  • رشته‌های یک فرآیند می‌توانند حافظه متغیرهای سراسری (Global Variables) را به‌اشتراک بگذارند؛ یعنی اگر مقدار متغیر در رشته تغییر کند، برای همه رشته‌ها قابل استفاده خواهد بود و دسترسی عمومی دارد.
  • Threading گزینه بسیارخوبی برای اجرای هم‌زمان چندین وظیفه محدود I/O است.

معایب Threading

  • پایتون براساس پیاده‌سازی CPython کار می‌کند و فقط یک رشته را در هرلحظه اجرا می‌کند؛ بنابراین، Threading ممکن است سرعت همه عملیات را افزایش ندهد. دلیل اصلی این موضوع، (GIL) است.
  • Threading برای سرعت‌بخشیدن به کارهای فشرده CPU، گزینه مناسبی نیست.
  • برنامه‌هایی که از رشته‌های مختلفی تشکیل شده‌اند، باید به‌سرعت بین رشته‌ها جابه‌جا شوند. به‌همین‌دلیل، در چنین برنامه‌هایی، باید از تکنیک زمان‌بندی (Scheduling) استفاده شود.
  • گاهی به‌اشتراک‌گذاری منابع در Threading مشکل‌ساز می‌شود؛ چراکه همه رشته‌ها منابع و حافظه یکسانی از متغیرهای سراسری را به‌اشتراک می‌گذارند. ازاین‌رو، عملیات انجام‌شده در یک رشته می‌تواند باعث خطای حافظه برای رشته‌های دیگر شود. همچنین ممکن است رشته‌های دیگر حافظه را وادار به انجام تسک‌های خود نکنند.

مزایای Coroutine در پایتون

  • اجرای عملیات به‌صورت غیرهمزمان
  • امکان پیاده‌سازی تکنیک‌های برنامه‌نویسی تابعی (Functional Programming)
  • بهره‌مندی ساده و سریع از مزایای زمان‌بندی پیش‌گیرانه (Pre-emptive Scheduling )
  • استفاده بهینه از سیستم و منابع آن
  • نیاز به منابع کمتر نسبت‌به رشته‌ها 
  • کاهش ضرورت برای قفل‌کردن منابع (Resource Locking)
  • افزایش محل مرجع (Locality of Reference)
  • افزایش پاسخ‌گویی برنامه‌ها به عملیات I/O
  • سینتکس‌های ساده‌تر در مقایسه با روش Callback یا رویکردهای مبتنی‌بر رشته
  • مدیریت آسان‌تر تسک‌های غیرهمزمان 
  • کاهش سربار روی حافظه و منابع پردازشی دستگاه

معایب Coroutine در پایتون

  • احتمال بروز خطا در کنترل جریان‌های پیچیده 
  • عدم نمایش منطق برنامه
  • ضرورت بر اجرای کنترل دستی به‌منظور کنترل منطق و جریان برنامه
  • احتمال هدررفتن منابع برنامه به‌دلیل عدم دقت در استفاده از برنامه‌های مشترک
  • دشواری در آموختن برای مبتدیان به‌دلیل منحنی یادگیری پرشیب

آموزش پیاده‌ سازی یک Coroutine ساده

پیش از یادگیری پیاده‌سازی یک Coroutine ساده، ابتدا باید چند نکته بسیار مهم را مرور کنیم:

  • یک Coroutine بااستفاده از “async def” تعریف می‌شود.
  • در داخل کوروتین، می‌توانید از “await” برای توقف اجرای کد تا زمانی‌که کار موردنظر کامل شود، استفاده کنید.
  • “await” برای بازگرداندن کنترل به حلقه رویداد استفاده می‌شود و به سایر تسک‌ها اجازه اجرا می‌دهد.
  • از این کلیدواژه فقط می‌توانید روی یک شیء با قابلیت “awaitable” استفاده کنید؛ مانند کوروتین‌های دیگر، تسک‌ها و Future ها.
  • کوروتین‌ها توسط یک حلقه رویداد (Event Loop) مدیریت می‌شوند که آن‌ها را زمان‌بندی و اجرا می‌کند.
  • حلقه رویداد چندین کار غیرهمزمان را انجام می‌دهد و مطمئن می‌شود که در زمان آماده بودن‌شان، اجرا می‌شوند.

حال می‌توانیم با شناخت این مفاهیم، به‌سراغ نحوه تعریف و استفاده از Coroutine برویم.

 

import asyncio
async def say_hello():
    print("Hello"
    await asyncio.sleep(1)  # Pauses here, allowing other tasks to run
    print("World")
# Running the coroutine
asyncio.run(say_hello())

 

  • تابع say_hello یک coroutine است؛ زیرا از “async def” استفاده می‌کند.
  • await asyncio.sleep(1) اجرای کوروتین را به‌مدت یک ثانیه متوقف می‌کند و به سایر تسک‌ها اجازه می‌دهد تا در این فاصله اجرا شوند.
  • برای اجرای کوروتین say_hello هم از تابع asyncio.run(say_hello()) استفاده کردیم. درنهایت، خروجی نهایی چاپ “Hello” است و پس از یک ثانیه، کلمه “World” چاپ می‌شود.

۴ روش مدیریت همزمانی و غیرهمزمانی با Coroutine در پایتون

برای مدیریت فرآیندهای همزمان و غیرهمزمان با Coroutine، می‌توانیم روش‌های زیر را پیاده کنیم.

۱. جداسازی موارد Async از Sync

تا حد امکان، کدهای همزمان و غیرهمزمان را ازهم جدا نگه دارید. بدین‌معناکه کدهای Async باید از توابع “async def” برای ساخت و از “await” برای مدیریت عملیات I/O-bound استفاده کنند. ازسوی دیگر، کدهای هم‌زمان باید از توابع معمولی بدون “async” و “await” استفاده کنند.

۲. استفاده از Wrappers برای ایجاد پل بین توابع همزمان و غیرهمزمان

اگر نیاز به فراخوانی کدهای غیرهمزمان از کدهای همزمان دارید، باید از توابع wrapper استفاده کنید. مثلاً:

 

def call_async(coro):
    return asyncio.run(coro)

 

این سینتکس به شما کمک می‌کند تا با تابع ()asyncio.run، یک کوروتین را از درون کد همزمان اجرا کنید.

فراخوانی توابع غیرهمزمان از کدهای همزمان ممکن است. اما این روش پیشنهاد نمی‌شود؛ چراکه برای هر فراخوانی، نیاز به ساخت و اجرای یک Event Loop داریم. نتیجه این چرخه، اغلب ناکارآمد خواهد بود.

۳. استفاده از ابزارهای همزمانی

برای اجرای چندین کوروتین به‌صورت موازی در یک لحظه، از ابزارهای همزمانی asyncio مانند ()asyncio.gather استفاده کنید. این ابزارها به حداکثر رساندن مزایای برنامه‌نویسی هم‌زمان را ممکن و ساده می‌کنند.

۴. استفاده از کتابخانه‌ های Async برای کدهای غیرهمزمان

هنگام نوشتن کدهای غیرهمزمان، از کتابخانه‌های طراحی‌شده برای این کار استفاده کنید؛ مانند aiohttp برای درخواست‌های HTTP. این کتابخانه‌ها API های سازگار با async را ارائه می‌دهند که به‌خوبی با asyncio کار می‌کنند.

۵ مورد استفاده عملی از Coroutine در پایتون

موارد استفاده از Coroutine در پایتون را می‌توانید در لیست زیر بخوانید.

  1. ماشین‌‌های حالت (State Machines)

پیاده‌سازی ماشین‌های حالت در یک زیربرنامه، روشی مؤثر است؛ جایی‌که وضعیت برنامه با نقطه ورود یا خروج فعلی رویه تعیین می‌شود. این عمل، منجر به افزایش خوانایی کد در مقایسه با استفاده از روش goto خواهد شد.

  1. مدل بازیگر (Actor Model)

Coroutine در پایتون برای اجرای مدل بازیگر به‌ روش هم‌زمانی بسیار مفید است. هر بازیگر رویه‌های خاص خود را دارد؛ اما کنترل را به زمان‌بندی مرکزی واگذار می‌کند تا این بخش بتواند به‌صورت متوالی، مدل‌ها را اجرا کند.

  1. مدل‌‌های مولدها (Generators)

Coroutine برای پیاده‌سازی مدل‌های مولدی که در جریان‌ها به‌کار می‌روند، به‌ویژه جریان‌های ورودی / خروجی، یک گزینه کاربردی است. همچنین متخصصان این تکنیک را یک رویکرد مؤثر برای پیمایش ساختارهای داده‌ای می‌دانند.

  1. برقراری ارتباط بین فرآیندهای متوالی

با کمک Coroutine می‌توان بین فرآیندهای متوالی پل ارتباطی ساخت؛ زیرفرآیندهایی که هرکدام از آن‌ها، یک Coroutine است. 

  1. ارتباط معکوس

 Coroutine در پایتون برای پیاده‌سازی ارتباط معکوس (Reverse Communication)، که اغلب در نرم‌افزارهای ریاضی استفاده می‌شود، کارآمد است؛ زیرا در این برنامه‌ها، یک رویه برای انجام محاسبات، به فرآیند خاصی نیاز دارد.

روش های رفع اشکالات و مشکلات رایج در کار با Coroutine در پایتون

برای رفع اشکالات رایج هنگام کار با Coroutine در پایتون، می‌توان چهار روش زیر را به‌کار گرفت.

استفاده از ()asyncio.run و await برای خطای Runtime

درصورت دریافت پیغام “RuntimeWarning: coroutine was never awaited”، باید از دو روش ()asyncio.run یا await، بسته‌به مشکل استفاده کنید.

  • اگر Coroutine نقطه ورود به کد غیرهمزمان است، از “()asyncio.run” برای اجرای آن استفاده کنید و کد زیر را بنویسید:

 

import asyncio
if __name__ == "__main__":
    asyncio.run(do_something_asynchronously())

 

  • اگر کد توسط یک تابع غیرهم‌زمان دیگر فراخوانی می‌شود، مطمئن شوید که “await” را به‌کار برده‌اید:

 

async def main():
    await do_something_asynchronously()

 

پیداکردن منابع تسک (Task References)

این روش برای رفع مشکلات مربوط‌به Coroutine های متوقف‌شده (آن‌هایی که درحال اجرا نیستند) به‌کار می‌رود. 

  • از ()asyncio.create_task برای ایجاد تسک‌ها و گرفتن منابع آن‌ها استفاده کنید. سپس، ()asyncio.wait را برای انتظار تا تکمیل تسک‌ها بنویسید:

 

async def do_hello():
    hello_task = asyncio.create_task(hello())
    world_task = asyncio.create_task(world())
    await asyncio.wait([hello_task, world_task])

 

بلوک try و except برای مدیریت استثناها

  • برنامه خود را در یک بلوک try و except قرار دهید تا استثناها را پیدا و مدیریت کنید:

 

async def failing_coroutine():
    raise Exception('This coroutine failed!')
try:
    asyncio.run(failing_coroutine())
except Exception as e:
    print(f'Caught an exception: {e}')

 

استفاده از ()asyncio.run برای دیباگ کردن

  • از ()asyncio.run برای اجرای کوروتین خود استفاده کنید و مطمئن شوید که به‌درستی اجرا می‌شود:

 

import asyncio
async def hello():
    await asyncio.sleep(1)
    print("Hello")
async def world():
    await asyncio.sleep(2)
    print("World")
async def do_hello():
    asyncio.create_task(hello())
    asyncio.create_task(world())
if __name__ == "__main__":
    asyncio.run(do_hello())

 

  • از ()asyncio.run برای اجرای Event Loop و مدیریت چرخه حیات Coroutine های خود استفاده کنید.
  • از ()asyncio.wait برای ایجاد وقفه و انتظار در زمان اجرای چندین کار استفاده کنید:

 

async def do_hello():
    hello_task = asyncio.create_task(hello())
    world_task = asyncio.create_task(world())
    await asyncio.wait([hello_task, world_task])

 

جمع بندی: Coroutine در پایتون

استفاده از Coroutine در پایتون سرعت فرآیند برنامه‌نویسی و کارایی یک برنامه را به‌شکل قابل قبولی افزایش می‌دهد. این تکنیک به‌سادگی و با دستور “async def” ساخته و با سینتکس “asyncio.run”، اجرا می‌شود. این روش مزایای متعددی دارد؛ مانند افزایش پاسخ‌گویی برنامه‌ها به عملیات I/O، استفاده بهینه از سیستم و نیاز به منابع کمتر نسبت‌به رشته‌ها. کاربرد دیگر این تابع در پایتون، افزایش کارایی مدل‌های هوش مصنوعی و اجرای چند دستور به‌صورت همزمان در نرم‌افزارها است.

ما خوشحال می‌شویم که در بخش نظرات، پاسخگوی کامنت شما باشیم و بدانیم با خواندن این مقاله، چه نکاتی را کارآمد و مؤثر می‌دانید؟

چه رتبه ای می‌دهید؟

میانگین ۵ / ۵. از مجموع ۱

اولین نفر باش

title sign
معرفی نویسنده
تیم فنی نیک آموز
مقالات
401 مقاله توسط این نویسنده
محصولات
0 دوره توسط این نویسنده
تیم فنی نیک آموز
title sign
معرفی محصول
title sign
دیدگاه کاربران

close-image