مار های ناهمزمان! ( async in python )

این مقاله در مورد روش های ناهمزمانی هست که توی پایتون میشه پیاده کرد البته بیشتر سعی کردم رو مفاهیم و دلیل استفاده از این روش و مزیت ها و معایب هاشون تمرکز کنم تا آموزش پیاده سازی اون ها کدهای مثال ها هم به زبان پایتون هست چون خودم پایتون بلدم :)) و اینکه درکش راحت تر از بقیه زبان هاست. مسلما این مقاله خالی از عیب نیست و من هم متخصص نیستم پس هر جا مشکل داشت حتما بگید

از اول شروع میکنیم اصلا این async (ناهمزمانی) و sync (همزمانی) که ما میگیم یعنی چی ؟ یه مثال خیلی خوب و گیرا همه چیز رو روشن میکنه. فرض کنید من رفتم به یه رستوران ایتالیایی و درخواست آبگوشت میدم اگه در مدتی که آبگوشت حاضر میشه من جلو پیشخوان وایسم و هیچ کاری نکنم من در واقع یه کار sync انجام دادم اگه تا وقتی که آبگوشت من حاضر میشه کار دیگه ای کنم مثلا قلیونی بکشم و وقتی که غذا حاضر شد صدام کردن و من رفتم آبگوشتم رو گرفتم من در واقع یه کار async انجام دادم البته میشه یه حرکت دیگه هم زد و اون اینه که یکی از دوستای بیکارمون رو بگیم وایسا جلو پیشخوان وقتی حاضر شد برامون بیار بعد خودمون بریم دوباره پی قلیونمون ! این حات هم async هست.

خب فکر کنم متوجه شده باشید منظورم چیه حالا همین مثال تو دنیای برنامه نویسی خیلی پیدا میشه از وایستادن برای جواب ریکوست یا ریسپانس تو نت بگیر تا جواب یه پردازش سنگین تصویر یا یه الگوریتم پیچیده و ...

روش هایی که تو پایتون وجود داره زیادن و اون هایی که من در موردشون اطلاع دارم عبارت اند از :

  • MultiProcessing ( چند پردازشی )
  • MultiThreading ( چند نخی )
  • AsyncIO (خروج و ورود ناهمزمان ( این دیگه چه اسمیه!! ) ) ( خ و ناهم )
  • MQ or message queue ( صف پیام )

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

MultiProcessing:

کلا من دوست دارم همه چیز رو از اول شروع کنم :))) پس میریم دنبال multiprocessing و کلا چی هست؟

در روش multiprocessing ما از هسته های سی پی یو و سیستم زمان بندی سیستم عامل استفاده میکنیم به این صورت که ما چند process ایجاد میکنیم و سیستم عامل هم به صورت عادلانه وقتی که یکی از process نیاز به پردازش داشت اون process رو به یکی از هسته ها میفرسه تا کاراش رو بکنه هر process فضا و اطلاعات خودش رو داره و process های مختلف نمیتونن اطلاعاتی که دارن رو با بقیه process ها به اشتراک بزارن یه مثال سادش تو پایتون اینه:

( تو این مکان به صورت عجیبی من نمیتونم کد ها رو بزارم و سایت ویرگول ارور 403 میده پس خودتون برید مثالش رو تو نت ببینید )

تو این روش سیستم عامل هر process که ساخته شده رو به صورت عادلانه از cpu بهره مند میکنه و اونهارو موقعی که نیاز بود یا نوبتشون شده بود به یکی از هسته های cpu میفرسته که کارشون رو بکنن و برنامه شما parallel ( موازی ) میشه و مسلما سرعتش هم بیشتر میشه در اینجا یه نکته وجود داره وقتی cpu شما یک هسته ای باشه دیگه کدای شما موازی اجرا نمیشه اما نیاز نیست اصلا نگران باشید چون شما روی شانه های غول ها وایستادید پس سیستم عامل همه کارارو براتون انجام میده و در هر زمان یکی از process هارو اجرا میکنه این موقع دیگه کد شما موازی نیست بهش میگن concurrency

نکته : در انگلیسی concurrency = synchronism ولی در برنامه نویسی concurrency ≠ synchronism

MultiThreading:

ثرد ها در واقع همون بچه process ها هستن که داخلی یک process ایجاد میشن و همون کار process رو انجام میدن و سیستم عامل هم برای زمان بندی تو اجرای اون ها بین process ها فرقی نمیزاره اماااااااااااااا یه فرق بزرگ و چند تا فرق کوچیک این وسط هست اولیش اینه که دیتاها تو ثرد ها میتونن به اشتراک گذاشته بشن به این شکل نگاه کنید:

همون طور که اون بالا میبینید همه thread ها به اطلاعات اشراک گذاشته شده دسترسی دارن فرق های کوچیکشون هم اینه که thread ها رم کمتری مصرف میکنن و زمانی که طول میکشه ساخته بشن کمتره ولی در عوض process ها کار باهاشون راحت تره و سرعتشون هم یه مقدار از thread ها بیشتره یه مثال تو پایتون این هست :

import threading 
import time 
import random   

def worker(number):    
    sleep = random.randrange(1, 10)     
    import threading   
     
for i in range(5):        
    t = threading.Thread(target=worker, args=(i,))     
    t.start()  
   
print("All Threads are queued, let's see when they finish!")


خیلی شبیه کدای multiprocessing هستن نه ؟ ولی هر کی که سیبیل که داره بابای من نیست :)))) این دوتا کد در باطن اصلا شبیه هم نیستن اصلا Cpython ( نسخه پیشفرض پایتون) نمیتونه موازی سازی با threading رو انجام بده! فقط یکی از اون ثرد ها هر لحظه اجرا میشه

یه لحظه وایسا چی؟؟؟؟؟؟؟؟؟؟؟ پس اونا چی بود گفتی ؟؟؟ گرفتی مارو حاجی ؟

خب برای درکه این مطلب باید یه playback بزنیم به وقتی که میخواستن Cpython رو درست کنن و از اول شروع کنیم ( بازم از اول :))) )

داستان از اینجا شروع میشه که دوستان عزیزی که میخواستن Cpython رو بسازم میخواستن هرچی لایبرری C دم دستشون هست بیارن وصل کنن به python و اکستنشن های اضافی هم در آینده راحت نوشته بشن اما یه مشکل وجود داشت ۱- بیشتر لایبرری های سی thread safe نیستن ۲- نوشتن کدی که thread safe باشه دقت و تجربه و زمان بیشتری میخواد ( کلا سخته ) پس گفتن حاجی بیخیال ورداشتن یه قفل گنده زدن رو چیزی که ساختن گذاشتن اسمش رو هم گذاشتن ( GIL (Global Interpreter Lock و تادددددامممم مشکل حل شد

نکته : تحقیق در مورد thread safe بر عهده دانش آموز است

خب یه سوال چرا میشه موازی سازی با multiprocessing کرد ولی نمیشه با multithreading کرد؟ جوابش تو تفاوت اصلی این دوتاس همون اطلاعات اشتراک گذاشته شده مشکل thread safe رو ایجاد میکنن از اونجا که توی multiprocessing اصلا این ویژگی نیست پس کلا این مشکل هم ایجاد نمیشه پس استفاده ازش بلا مانع هست

شاعر در این لحظه میگوید: شت ! خب حالا که نمیتونیم از این ویژگی خفن استفاده کنیم باید چیکار کنیم؟

۳ حرکت میتونیم بزنیم اولی اینکه این کار رو بسپاریم به یه چیزی خارج از از Cpython یا از یه نسخه پایتون استفاده کنیم که GIL نداره یا برای سر بار کمتر از Green thread استفاده کنیم

آخریه چی هست؟ در واقع شبیه سازی کار ثردها روی یه ثرد هست یعنی شما میتونی هر بخش از کدت رو در هر لحظه اجرا کنی اما نمیتونی همزمان اجرا کنی یعنی همون thread با GIL که تو پایتون هست ( thread های پایتون واقعیین) اما چون thread ها توسط لایبری ها ساخته میشن هم سرعت ساخته شدنشون بیشتره هم رم کمتری میگرن هم سرعت سویچشون بیشتره اما خب کار باهاش یکم سخت تره (یه خرده)

اینم یه شکل که درک کار GIL رو راحت میکنه

AsyncIO :

اسینک آی ا‌ٌ در واقع خیلی جدید نیست قبلا یه پروژه third-party بوده ولی الان بخشی از پایتون شده و خیلی هم محبوب هست این کاری که asyncio انجام میده کار جدیدی هم نیست توی زبان ها و فریمورک های دیگه هم وجود داره مثلا nodejs

خب اصلا این asyncio چی هست ؟ چی کار میکنه ؟ و وقتی multithreading و multiprocessing هست چرا اصلا از این استفاده کنیم ؟

اسینک آی ا‌ٌ چیز خوبیه :))) (اگه خوب نبود که ۲ تا keyword براش توی پایتون نمیدادن ) یه کتابخونه برای ایجاد concurrency توی برنامه های شما هست و از اسمش هم معلوم هست برای io طراحی شده مثلا وقتی میخواید ۱۰ تا ریکویست بزنید دیگه لازم نیست یه ریکویست بزنید بعد منتظر جواب بمونید بعد دوباره یکی دیگه بزنید و ... همه اون ها رو یه جا ایجاد میکنید بعد منتظر جواب میمونید و وقتی جواب اومد یه event ران میشه که بقیه کد شما رو اجرا میکنه و همه این ها روی یه thread یا process انجام میشه

یه مثال:

import asyncio
import datetime
import random

async def my_sleep_func():
    await asyncio.sleep(random.randint(0, 5))

async def display_date(num, loop):
    end_time = loop.time() + 50.0    
    while True:
        print("Loop: {} Time: {}".format(num, datetime.datetime.now()))
        if (loop.time() + 1.0) >= end_time:
                break
        await my_sleep_func()
        
loop = asyncio.get_event_loop()

asyncio.ensure_future(display_date(1, loop))
asyncio.ensure_future(display_date(2, loop))

loop.run_forever()

الان سوال سوم واقعا چرا ؟ جواب : بخاطر GIL ( مثل اینکه همه چیز تقصیر اینه) واقعا وقتی از ویژگی موازی سازی ثرد ها نمیتونیم استفاده کنیم که برای این طراحی شدن اصلا چرا ازشون استفاده کنیم؟ مسلمه asyncio رم خیلی کمتری مصرف میکنه و محدودیتی هم توی ایجاد event نداره

اما greenthread هم هست که هم میشه تعداد زیادی thread توشون spawn کرد و رم کمی هم مصرف میکنن پس اونا چی ؟ خب تفاوتشون توی محدودیت این green thread هاست از اونجا که اینا همون شبیه سازیه thread واقعی هستن پس محدودیت های thread های اون ها رو هم دارن مثل thread safe بودن یا ارتباط نداشتن thread ها به هم دیگه ( مثلا کال کردن یه متد توی یه ثرد از یه ثرد دیگه) و پیچیده شدن کد ها و ... از اونجا که asyncio به صورت رادیکالی متفاوت پیاده سازی شده پس اون مشکل ها رو نداره و شما میتونید با خیال راحت کاراتون رو بکنید به همین راحتی

امیدوارم که مفید واقع شده باشه و جالب باشه براتون در ادامه لیستی از کلمات کلیدی میدم که خواستید سرچ کنید توی این موضوع به درد میخوره

asyncio , celery , MQ , rabbitmq , sub pub , multithreading , multiprocessing , thread safe , sync , concurrency , greenlet , greenthread , gunicorn , uwsgi

منابع :

https://labs.getninjas.com.br/go-vs-cpython-visual-comparison-of-concurrency-and-parallelism-d29a1ebec20a#targetText=AsyncIO%20and%20Gevent%2Dish%20options,controls%20the%20green%20thread%20switching.

http://masnun.rocks/2016/10/06/async-python-the-different-forms-of-concurrency/

سایت medium , ویکی پدیا و stackoverflow و quora و هر چی دم دستم اومده...

اینم اگه دوست دارید بیشتر در مورد thread ها بخونید :

https://virgool.io/@GreatBahram/once-for-ever-parallelism-1-iqyzjwqmks0n