از طریق منوی جستجو مطلب مورد نظر خود در وبلاگ را به سرعت پیدا کنید
همزمانی در پایتون
سرفصلهای مطلب
معرفی
محاسبات در طول زمان تکامل یافته است و راههای بیشتری برای سریعتر کردن رایانهها به وجود آمده است. چه می شود اگر به جای اجرای یک دستور در یک زمان، بتوانیم چندین دستور را همزمان اجرا کنیم؟ این به معنای افزایش قابل توجه عملکرد یک سیستم است.
از طریق همزمانی، ما میتوانیم به این هدف برسیم و برنامههای پایتون ما میتوانند درخواستهای بیشتری را در یک زمان واحد رسیدگی کنند و در طول زمان منجر به افزایش عملکرد چشمگیر شوند.
در این مقاله، همزمانی در زمینه برنامهنویسی پایتون، اشکال مختلف آن را مورد بحث قرار میدهیم و به یک برنامه ساده سرعت میدهیم تا در عمل شاهد افزایش عملکرد آن باشیم.
Concurrency چیست؟
زمانی که دو یا چند رویداد هستند هم زمان، به این معنی است که آنها همزمان در حال وقوع هستند. در زندگی واقعی، همزمانی امری رایج است زیرا بسیاری از چیزها همیشه در یک زمان اتفاق میافتند. در محاسبات، وقتی صحبت از همزمانی به میان می آید، همه چیز کمی متفاوت است.
در محاسبات، همزمانی اجرای تکههای کار یا وظایف توسط یک کامپیوتر به طور همزمان است. به طور معمول، یک کامپیوتر یک قطعه کار را در حالی اجرا می کند که دیگران در انتظار نوبت خود هستند، پس از تکمیل آن، منابع آزاد می شوند و کار بعدی شروع به اجرا می کند. این مورد زمانی نیست که همزمانی اجرا شود زیرا کارهایی که باید اجرا شوند همیشه نباید منتظر بمانند تا کارهای دیگر تکمیل شوند. آنها در همان زمان اعدام می شوند.
همزمانی در مقابل موازی گرایی
ما همزمانی را به عنوان اجرای همزمان وظایف تعریف کرده ایم، اما چگونه با موازی سازی مقایسه می شود و چیست؟
موازی سازی زمانی حاصل می شود که چندین محاسبات یا عملیات به طور همزمان یا موازی با هدف افزایش سرعت محاسبات انجام شوند. process.
همزمانی و موازی بودن هر دو با انجام چندین کار به طور همزمان درگیر هستند، اما چیزی که آنها را متمایز می کند این واقعیت است که در حالی که همزمانی فقط در یک پردازنده اتفاق می افتد، موازی بودن از طریق استفاده از چندین CPU برای انجام وظایف به صورت موازی به دست می آید.
Thread vs Process vs Task
در حالی که به طور کلی، موضوعات، فرآیندها و وظایف ممکن است به قطعات یا واحدهای کار اشاره داشته باشند. با این حال، در جزئیات آنها چندان مشابه نیستند.
thread کوچکترین واحد اجرایی است که می توان انجام داد روی یک کامپیوتر. موضوعات به عنوان بخش هایی از a وجود دارند process و معمولاً مستقل از یکدیگر نیستند، به این معنی که آنها داده ها و حافظه را با رشته های دیگر در همان یک به اشتراک می گذارند process. رزوه ها گاهی اوقات به عنوان فرآیندهای سبک وزن نیز شناخته می شوند.
به عنوان مثال، در یک برنامه پردازش سند، یک رشته میتواند مسئول قالببندی متن باشد و دیگری ذخیره خودکار را انجام میدهد، در حالی که دیگری در حال بررسی املا است.
آ process یک کار یا نمونه ای از یک برنامه محاسبه شده است که می تواند اجرا شود. وقتی کد می نویسیم و اجرا می کنیم، الف process برای اجرای تمام وظایفی که به کامپیوتر دستور داده ایم از طریق کد ما انجام دهد ایجاد شده است. آ process می تواند یک رشته اصلی داشته باشد یا چندین رشته درون آن داشته باشد که هر کدام دارای پشته، ثبات ها و شمارنده برنامه خاص خود هستند. اما همه آنها کد، داده و حافظه را به اشتراک می گذارند.
برخی از تفاوت های مشترک بین فرآیندها و نخ ها عبارتند از:
- فرآیندها به صورت مجزا کار می کنند در حالی که رشته ها می توانند به داده های رشته های دیگر دسترسی داشته باشند
- اگر یک نخ در یک process مسدود شده است، رشته های دیگر می توانند به اجرا ادامه دهند، در حالی که a مسدود شده است process قرار خواهد داد روی اجرای سایر فرآیندها را در صف نگه دارید
- در حالی که thread ها حافظه را با رشته های دیگر به اشتراک می گذارند، فرآیندها و هر کدام این کار را انجام نمی دهند process تخصیص حافظه خاص خود را دارد.
یک وظیفه به سادگی مجموعه ای از دستورالعمل های برنامه است که در حافظه بارگذاری می شوند.
Multithreading در مقابل Multiprocessing در مقابل Asyncio
پس از بررسی رشته ها و فرآیندها، اجازه دهید اکنون به روش های مختلفی که یک رایانه همزمان اجرا می کند عمیق تر بپردازیم.
Multithreading به توانایی یک CPU برای اجرای چند رشته به صورت همزمان اشاره دارد. ایده در اینجا تقسیم a است process به رشته های مختلف که می توانند به صورت موازی یا همزمان اجرا شوند. این تقسیم وظیفه سرعت اجرای کل را افزایش می دهد process. به عنوان مثال، در یک واژه پرداز مانند MS Word، چیزهای زیادی در حال انجام است روی هنگام استفاده
Multithreading به برنامه این امکان را می دهد که محتوای در حال نوشتن را به صورت خودکار ذخیره کند، املای محتوا را بررسی کند و همچنین محتوا را قالب بندی کند. از طریق multithreading، همه اینها می تواند به طور همزمان انجام شود و کاربر مجبور نیست ابتدا سند را تکمیل کند تا ذخیره اتفاق بیفتد یا بررسی املا انجام شود.
تنها یک پردازنده در طول چند رشته ای درگیر است و سیستم عامل تصمیم می گیرد که چه زمانی وظایف را در پردازنده فعلی تغییر دهد، این وظایف ممکن است خارج از جریان فعلی باشند. process یا برنامه در حال اجرا در پردازنده ما.
پردازش چندگانه، روی از سوی دیگر، شامل استفاده از دو یا چند واحد پردازنده است روی یک کامپیوتر برای رسیدن به موازی سازی پایتون با ایجاد فرآیندهای مختلف برای برنامههای مختلف، چند پردازش را پیادهسازی میکند، که هر کدام نمونهای از مفسر پایتون برای اجرا و تخصیص حافظه برای استفاده در طول اجرا دارند.
AsyncIO یا IO ناهمزمان یک پارادایم جدید است که در پایتون 3 به منظور نوشتن کد همزمان با استفاده از syntax async/wait معرفی شده است. برای مقاصد شبکه ای محدود و سطح بالا بهترین است.
زمان استفاده از Concurrency
هنگام حل مسائل مربوط به CPU یا IO به بهترین وجه از مزایای همزمانی استفاده می شود.
مشکلات مربوط به CPU شامل برنامه هایی است که محاسبات زیادی را بدون نیاز به شبکه یا امکانات ذخیره سازی انجام می دهند و فقط با قابلیت های CPU محدود می شوند.
مشکلات مربوط به IO شامل برنامه هایی است که متکی هستند روی منابع ورودی/خروجی که گاهی ممکن است کندتر از CPU باشند و معمولاً در حال استفاده هستند، بنابراین، برنامه باید منتظر کار فعلی باشد تا منابع ورودی/خروجی را آزاد کند.
زمانی که منابع CPU یا I/O محدود هستند و می خواهید سرعت برنامه خود را افزایش دهید، بهتر است کد همزمان بنویسید.
روش استفاده از Concurrency
در مثال نمایشی خود، یک مشکل رایج محدود به ورودی/خروجی را حل خواهیم کرد، که دانلود فایل ها از طریق شبکه است. ما کدهای غیرهمزمان و کدهای همزمان را می نویسیم و مدت زمان تکمیل هر برنامه را با هم مقایسه می کنیم.
ما تصاویر را از ایمگور از طریق API آنها. ابتدا باید یک حساب کاربری بسازیم و سپس ثبت نام برنامه آزمایشی ما به منظور دسترسی به API و دانلود برخی از تصاویر.
هنگامی که برنامه ما راه اندازی شد روی Imgur، ما یک شناسه کلاینت و راز سرویس گیرنده دریافت خواهیم کرد که از آن برای دسترسی به API استفاده خواهیم کرد. ما اعتبارنامه ها را در یک ذخیره می کنیم .env
فایل از Pipenv به طور خودکار بارگیری می شود متغیرها از .env
فایل.
اسکریپت همزمان
با این جزئیات، ما می توانیم اولین اسکریپت خود را ایجاد کنیم که به سادگی تعدادی از تصاویر را در a دانلود می کند downloads
پوشه:
import os
from urllib import request
from imgurpython import ImgurClient
import timeit
client_secret = os.getenv("CLIENT_SECRET")
client_id = os.getenv("CLIENT_ID")
client = ImgurClient(client_id, client_secret)
def download_image(link):
filename = link.split('/')(3).split('.')(0)
fileformat = link.split('/')(3).split('.')(1)
request.urlretrieve(link, "downloads/{}.{}".format(filename, fileformat))
print("{}.{} downloaded into downloads/ folder".format(filename, fileformat))
def main():
images = client.get_album_images('PdA9Amq')
for image in images:
download_image(image.link)
if __name__ == "__main__":
print("Time taken to download images synchronously: {}".format(timeit.Timer(main).timeit(number=1)))
در این اسکریپت، یک شناسه آلبوم Imgur را پاس می کنیم و سپس تمام تصاویر آن آلبوم را با استفاده از تابع دانلود می کنیم. get_album_images()
. این لیستی از تصاویر را به ما می دهد و سپس از تابع خود برای دانلود تصاویر و ذخیره آنها در یک پوشه به صورت محلی استفاده می کنیم.
این مثال ساده کار را انجام می دهد. ما می توانیم تصاویر را از ایمگور دانلود کنیم اما همزمان کار نمی کند. قبل از حرکت فقط یک تصویر را در یک زمان دانلود می کند روی به تصویر بعدی در دستگاه من، اسکریپت 48 ثانیه طول کشید تا تصاویر را دانلود کرد.
بهینه سازی با Multithreading
اجازه دهید اکنون کد خود را با استفاده همزمان انجام دهیم چند رشته ای و روش عملکرد آن را ببینید:
import threading
from concurrent.futures import ThreadPoolExecutor
def download_album(album_id):
images = client.get_album_images(album_id)
with ThreadPoolExecutor(max_workers=5) as executor:
executor.map(download_image, images)
def main():
download_album('PdA9Amq')
if __name__ == "__main__":
print("Time taken to download images using multithreading: {}".format(timeit.Timer(main).timeit(number=1)))
در مثال بالا، a را ایجاد می کنیم Threadpool
و 5 رشته مختلف برای دانلود تصاویر از گالری ما راه اندازی کنید. اجرای موضوعات را به خاطر بسپارید روی یک پردازنده
این نسخه از کد ما 19 ثانیه طول می کشد. این تقریباً سه برابر سریعتر از نسخه همزمان اسکریپت است.
بهینه سازی با چند پردازش
اکنون اجرا خواهیم کرد پردازش چندگانه در چندین CPU برای یک اسکریپت برای مشاهده عملکرد آن:
import multiprocessing
def main():
images = client.get_album_images('PdA9Amq')
pool = multiprocessing.Pool(multiprocessing.cpu_count())
result = pool.map(download_image, (image.link for image in images))
if __name__ == "__main__":
print("Time taken to download images using multiprocessing: {}".format(timeit.Timer(main).timeit(number=1)))
در این نسخه ما یک Pool ایجاد می کنیم که شامل تعداد هسته های CPU است روی دستگاه ما و سپس نقشه عملکرد ما برای دانلود تصاویر در سراسر استخر. این باعث می شود کد ما به صورت موازی در سراسر CPU اجرا شود و این نسخه چند پردازشی کد ما به طور متوسط 14 ثانیه پس از چندین بار اجرا طول می کشد.
این کمی سریعتر از نسخه ما که از رشته ها استفاده می کند و به طور قابل توجهی سریعتر از نسخه غیر همزمان ما است.
بهینه سازی با AsyncIO
اجازه دهید همان اسکریپت را با استفاده از آن پیاده سازی کنیم AsyncIO برای مشاهده عملکرد آن:
import asyncio
import aiohttp
async def download_image(link, session):
"""
Function to download an image from a link provided.
"""
filename = link.split('/')(3).split('.')(0)
fileformat = link.split('/')(3).split('.')(1)
async with session.get(link) as response:
with open("downloads/{}.{}".format(filename, fileformat), 'wb') as fd:
async for data in response.content.iter_chunked(1024):
fd.write(data)
print("{}.{} downloaded into downloads/ folder".format(filename, fileformat))
async def main():
images = client.get_album_images('PdA9Amq')
async with aiohttp.ClientSession() as session:
tasks = (download_image(image.link, session) for image in images)
return await asyncio.gather(*tasks)
if __name__ == "__main__":
start_time = timeit.default_timer()
loop = asyncio.get_event_loop()
results = loop.run_until_complete(main())
time_taken = timeit.default_timer() - start_time
print("Time taken to download images using AsyncIO: {}".format(time_taken))
تغییرات کمی در فیلمنامه جدید ما وجود دارد. اول اینکه ما دیگر از حالت عادی استفاده نمی کنیم requests
ماژول برای دانلود تصاویر ما، اما در عوض ما استفاده می کنیم aiohttp
. دلیل این امر این است که requests
با AsyncIO ناسازگار است زیرا از Python استفاده می کند http
و sockets
مدول.
سوکت ها به طور طبیعی مسدود هستند، یعنی نمی توان آنها را متوقف کرد و اجرا بعدا ادامه می یابد روی. aiohttp
این را حل می کند و به ما کمک می کند تا به کدهای ناهمزمان واقعی دست یابیم.
کلمه کلیدی async
نشان می دهد که تابع ما a است کوروتین (روتین تعاونی)، کدی است که می توان آن را مکث کرد و از سر گرفت. چند کار را به صورت مشترک انجام می دهد، به این معنی که آنها زمان توقف را انتخاب می کنند و اجازه می دهند دیگران آن را اجرا کنند.
ما یک استخر ایجاد می کنیم که در آن یک صف از تمام پیوندهای تصاویری که می خواهیم بارگیری کنیم ایجاد می کنیم. برنامه ما با قرار دادن آن در حلقه رویداد و اجرای آن تا تکمیل شروع می شود.
پس از چندین بار اجرا از این اسکریپت، AsyncIO نسخه 14 ثانیه طول می کشد روی متوسط برای دانلود تصاویر آلبوم. این به طور قابل توجهی سریعتر از نسخه های چند رشته ای و همزمان کد است و بسیار شبیه به نسخه چند پردازشی است.
مقایسه عملکرد
همزمان | چند رشته ای | پردازش چندگانه | Asyncio |
---|---|---|---|
دهه 48 | دهه 19 | 14 ثانیه | 14 ثانیه |
نتیجه
در این پست به همزمانی و مقایسه آن با موازی سازی پرداخته ایم. ما همچنین روشهای مختلفی را که میتوانیم برای پیادهسازی همزمانی در کد پایتون خود از جمله multithreading و multiprocessing استفاده کنیم، بررسی کردهایم و همچنین تفاوتهای آنها را مورد بحث قرار دادهایم.
از مثالهای بالا، میتوانیم ببینیم که چگونه همزمانی به کد ما کمک میکند سریعتر از آن به صورت همزمان اجرا شود. به عنوان یک قاعده کلی، Multiprocessing برای کارهای محدود به CPU مناسب است در حالی که Multithreading برای کارهای I/O-bound بهترین است.
کد منبع این پست موجود است روی GitHub برای مرجع.
(برچسبها به ترجمه)# python
منتشر شده در 1403-01-22 06:34:05