از طریق منوی جستجو مطلب مورد نظر خود در وبلاگ را به سرعت پیدا کنید
ورود به دنیای همزمانی با پایتون
سرفصلهای مطلب
در این آموزش به بررسی همزمانی در پایتون می پردازیم. ما در مورد Threads و Process ها و اینکه چگونه آنها مشابه و متفاوت هستند بحث خواهیم کرد. شما همچنین در مورد Multi-threading، Multi-processing، Asynchronous Programming و Concurrency به طور کلی در پایتون خواهید آموخت.
بسیاری از آموزش ها این مفاهیم را مورد بحث قرار می دهند، اما به دلیل عدم وجود مثال های واضح، درک آنها می تواند چالش برانگیز باشد. من شما را از طریق مفاهیم راهنمایی می کنم و به دنیای Concurrency در پایتون می پردازم و توضیحاتی ساده برای درک بهتر ارائه می دهم.
پیش نیازها
شما باید با کدنویسی پایتون آشنا باشید و درک اولیه ای از چند رشته ای داشته باشید.
اگرچه من برای کمک به کسانی که با این مفاهیم آشنا نیستند، نمونههایی از کدهای غیر چند رشتهای ارائه کردهام، اگر قبلاً در این زمینهها دانش دارید، میتوانید درک عمیقتری کسب کنید.
فهرست مطالب
- برنامه نویسی متوالی
- موضوعات چیست؟
- فرآیندها چیست؟
- موضوعات در مقابل فرآیندها
- Multi-threading چیست؟
- چند پردازش چیست؟
- برنامه نویسی آسنکرون چیست؟
- Concurrency چیست؟
- قفل مترجم جهانی (GIL)
- چگونه محدودیت های GIL را حذف کنیم
– استفاده از طعم دیگری از پایتون
– ماژول چند پردازشی
– آسینسیو - خلاصه
برنامه نویسی متوالی
با دیدن کلمه “Concurrency” نترسید. فعلاً آن را کنار بگذاریم.
وقتی شروع به یادگیری زبان های برنامه نویسی مانند پایتون یا جاوا می کنید، برنامه شما معمولاً به صورت a اجرا می شود sequence
، از بالا شروع می شود و به پایین می رود.
مثلا:
a = 25
b = 30
def add(a, b):
print("Addition value: ", a+b)
def sub(a, b):
print("Subtraction Value: ", a-b)
add(a,b)
sub(a,b)
هنگامی که برنامه بالا را اجرا می کنید، مفسر پایتون شما شروع به اجرای خط به خط آن از بالا به پایین در یک دنباله می کند. از خط شروع می شود a=25
و به پایان می رسد sub()
تابع.
مهم نیست کد شما چقدر طولانی باشد، پایتون آن را از بالا اجرا می کند. به این روش اجرای کد نیز معروف است بالا به پایین یا متوالی رویکرد – یا به سادگی برنامه نویسی متوالی!
موضوعات چیست؟
هنگامی که برنامه خود را اجرا می کنید، مفسر پایتون یک برنامه کوچک را در داخل ایجاد می کند و به آن دستور می دهد که شروع به اجرا کند. این برنامه کوچک که توسط مفسر ایجاد شده است، a نامیده می شود نخ. thread یک برنامه کوچک است که وظیفه خاصی را انجام می دهد.
برای هر برنامه یا برنامه ای، پایتون یک رشته ایجاد می کند و به آن دستور می دهد تا اجرا را شروع کند. به این رشته می گویند Main thread
. توجه به این نکته ضروری است که Main thread
همچنین اگر به او بگویید این کار را انجام دهد می تواند چندین رشته دیگر ایجاد کند.
این درست است! می توانید برنامه ای بنویسید تا به رشته اصلی بگویید چندین رشته دیگر ایجاد کند که وظایف شما را انجام می دهند. بنابراین، یک رشته کار یا وظیفه ای را انجام می دهد (در اینجا رشته اصلی برنامه شما را از بالا به پایین اجرا می کند).
بنابراین می توانید چندین کار ایجاد کنید و یک رشته را برای انجام یک کار اختصاص دهید. یا میتوانید یک رشته ایجاد کنید و چندین کار را اجرا کنید – درست مانند مفسر پایتون Main thread
و هر دو کار را اجرا کرد add
و sub
به ترتیب متوالی
فرآیندها چیست؟
تصور کنید صاحب یک شرکت هستید و وظایف زیادی برای انجام دادن دارید. برای رسیدگی به آنها، شروع به استخدام افراد و تعیین تکلیف به هر فرد خواهید کرد.
هر فرد به طور مستقل کار می کند روی وظیفه آنها آنها مجموعه ای از ابزارها و منابع مخصوص به خود را دارند و اگر یک نفر کار خود را به پایان برساند، مستقیماً بر کاری که دیگران انجام می دهند تأثیر نمی گذارد.
در دنیای کامپیوتر، هر فردی که یک کار را انجام می دهد مانند یک کار جداگانه است process. فرآیندها مستقل هستند، فضای حافظه مخصوص به خود را دارند و مستقیماً منابع را به اشتراک نمی گذارند.
به عنوان مثال، هنگامی که شما می چرخید روی کامپیوتر شما و شروع به باز کردن یک مرورگر، دفترچه یادداشت و MS Office، شما اساسا چندین فرآیند را شروع می کنید. هر برنامه ای که باز می کنید، مانند مرورگر یا دفترچه یادداشت، جداگانه است process با خاطره خودش این فرآیندها به یکدیگر متصل نیستند.
هنگامی که یک برنامه را اجرا می کنید، سیستم عامل شما یک A را ایجاد می کند process، مقداری حافظه را به آن اختصاص می دهد، مقداری از زمان CPU را اختصاص می دهد و برنامه را در آن اجرا می کند. روش عملکرد برنامه در داخل process با روش طراحی آن تعیین می شود. در اصل، می توان گفت که یک برنامه کاربردی a process.
موضوعات در مقابل فرآیندها
حالا، ممکن است گیج کننده به نظر برسد، درست است؟ اما تفاوت کوچکی بین Thread ها و Process ها وجود دارد. بگذارید آن را برای شما روشن کنم.
آ process مانند یک وظیفه واحد است که توسط سیستم عامل انجام می شود. کار می کند روی خود را دارد و فضای حافظه مخصوص به خود را دارد.
یک رشته مانند یک بخش کوچکتر از یک کار در یک برنامه است. کارهای کوچک را انجام می دهد. موضوعات ایجاد می شود در یک process و توسط برنامه کنترل می شوند.
یک برنامه می تواند چندین رشته ایجاد کند و آنها می توانند منابع را به اشتراک بگذارند، با یکدیگر ارتباط برقرار کنند، اما فقط در همان یک. process.
برای ساده کردن، مرورگر را به عنوان یک در نظر بگیرید process. در این process، می تواند یک یا چند رشته را اجرا کند.
هنگامی که در مورد همزمانی در برنامه نویسی یاد می گیرید، عباراتی مانند “Multi-Threading”، “Multi-Processing”، “Asynchronous” و در نهایت بزرگ “Concurrency” را خواهید دید. اجازه ندهید این اصطلاحات شما را بترسانند – مفاهیم ساده تر از آن چیزی هستند که فکر می کنید. و آنها چیزی هستند که در ادامه در مورد آنها خواهید آموخت.
Multi-threading چیست؟
زندگی آسان تر است وقتی کارگران زیادی برای انجام تمام وظایف ضروری دارید، اینطور نیست؟ به طور مشابه، اگر چندین رشته داشته باشید، به شما کمک می کنند تا تمام وظایف برنامه خود را به بخش های کوچکتر تقسیم کنید.
البته، اگر کارگران زیادی دارید، مدیریت آنها می تواند دشوار باشد و هزینه بیشتری نیز دارد. اینها مزایا و معایب Multi-threading نیز هستند.
استفاده از چندین رشته برای انجام وظایف خود نامیده می شود چند رشته ای.
بیایید مثال را دوباره مرور کنیم:
a = 25
b = 30
def add(a, b):
print("Addition value: ", a+b)
def sub(a, b):
print("Subtraction Value: ", a-b)
add(a,b)
sub(a,b)
می توانید به مفسر پایتون بگویید دو رشته ایجاد کند و آن را اجرا کند add()
با نخ-1 و sub()
با نخ-2. این خوب است، اما چرا می خواهید این کار را انجام دهید؟
رویکرد متوالی را در نظر بگیرید: مفسر اجرا می شود add()
اول، سپس sub()
. چرا منتظر sub()
در حالی که add()
در حال اجراست؟ در این مورد، جمع و تفریق کاملاً متفاوت هستند و به آن بستگی ندارند روی یکدیگر، پس چرا صبر کنید؟ آیا هر دو تابع به طور همزمان اجرا نمی شوند؟
اینجاست که چندین رشته وارد بازی می شوند. اگر وظایف شما مستقل هستند و نیازی به صبر کردن برای تکمیل یکدیگر ندارید، این امتیاز را دارید که وظایف خود را با استفاده از چندین رشته اجرا کنید.
توجه داشته باشید که چه یک رشته یا چند رشته ایجاد کنید، مفسر پایتون شما آن را ایجاد خواهد کرد موضوع اصلی و اجرای برنامه خود را مدیریت کنید. اگر به برنامه خود دستور ایجاد 2 رشته را بدهید، موضوع اصلی آنها را برای شما ایجاد می کند و تعیین می کند که هر رشته کدام وظیفه را باید انجام دهد.
بنابراین، صرف نظر از اینکه در برنامه خود یک Thread ایجاد کنید یا نه، موضوع اصلی ایجاد می شود و اجرای برنامه شما را بر عهده می گیرد.
چند پردازش چیست؟
از خود نام، ممکن است بتوانید درک کنید که چند پردازش به معنای اجرای چندین فرآیند است بصورت جداگانه بدون به اشتراک گذاری مستقیم منابع
به عنوان مثال، دو رستوران را تصور کنید که هر کدام سرآشپزهای خاص خود را دارند. روشی که یکی از سرآشپزها در رستوران 1 برای پختن غذا استفاده می کند، تأثیری ندارد یا متکی نیست روی رستوران 2. آنها به طور مستقل عمل می کنند، حتی اگر در هر دو رستوران یک سفارش بدهید.
اما ممکن است از خود بپرسید: چگونه در مورد چندوظیفگی صحبت کنیم روی کامپیوتری با یک پردازنده اما تعداد زیادی هسته پردازنده؟
در رایانه های مدرن، برخی از پردازنده ها دارای چندین هسته هستند و هر هسته مانند پردازنده خود کار می کند. اگر پردازنده ای 4 هسته داشته باشد، می تواند با اختصاص دادن هر کار به یک هسته متفاوت، 4 کار را به طور همزمان انجام دهد. این بدان معناست که CPU، حافظه و منابع ذخیره سازی بین این وظایف بدون اتکا به اشتراک گذاشته می شوند روی یکدیگر.
این رویکرد به چندوظیفه روی چند هسته در یک پردازنده به نام چند پردازش متقارن.
اگر چندین پردازنده دارید، می توانید هر وظیفه را به یک پردازنده متفاوت اختصاص دهید تا به طور مستقل اجرا شود. این نوع پردازش چندگانه نامیده می شود چند پردازشی توزیع شده
من می خواهم به شما یادآوری کنم که چند رشته ای رخ می دهد در داخل آ process. به زبان ساده، چند رشته ای فقط در یک اتفاق می افتد process.
بیایید درک عملی از چند پردازش به دست آوریم. من از همان مثالی استفاده خواهم کرد که در بخش برنامه نویسی متوالی و چند رشته ای بحث کردیم، اما این بار از یک نسخه چند پردازشی استفاده می کنم.
همانطور که شما برنامه را با استفاده از thread ها اجرا کردید، اکنون برنامه را با استفاده از فرآیندها اجرا خواهیم کرد.
from multiprocessing import Process
a = 25
b = 30
def add(a, b):
try:
print("Addition value: ", a + b)
time.sleep(10)
except Exception as e:
print(e)
def sub(a, b):
try:
print("Subtraction Value: ", a - b)
time.sleep(20)
except Exception as e:
print(e)
if __name__ == "__main__":
# Create two processes, one for add and one for sub
add_process = Process(target=add, args=(a, b))
sub_process = Process(target=sub, args=(a, b))
# Start the processes
add_process.start()
sub_process.start()
# Wait for both processes to finish
add_process.join()
sub_process.join()
ما در حال استفاده از multiprocessing
ماژول در این برنامه برای ایجاد 2 فرآیند: add_process
و sub_process
. یکی process اجرا می کند add()
تابع و دیگری اجرا می کند sub()
عملکرد، به ترتیب.
سپس هر دو فرآیند را با استفاده از .start()
روش هر دو process اشیاء. پس از این، ما در حال استفاده از .join()
روش از process اشیاء برای ساختن main thread
صبر کنید تا هر دو فرآیند کامل شوند.
بیایید این را با جزئیات بیشتری درک کنیم. من برنامه را در یک کامپیوتر مبتنی بر سیستم عامل ویندوز اجرا می کنم. باز کن task manager
برنامه ای برای مشاهده تمام فرآیندهای در حال اجرا در سیستم عامل شما. برای اجرای برنامه خود باید از ویندوز خود استفاده کنید command prompt
یا cmd
یا شما terminal
در لینوکس یا مک شما
تصویر بالا مدیر وظیفه را نشان می دهد روی سمت چپ و CMD روی حق شما به راحتی می توانید پردازشگر فرمان ویندوز یا CMD را ببینید که به صورت a اجرا می شود process در مدیر وظیفه شما همچنین می بینید که من در حال دویدن هستم Google Chrome
و Snipping Tool
در پسزمینه، که هر دو در Task Manager من نیز فهرست شدهاند.
اکنون با اجرای دستور، برنامه را از CMD اجرا می کنم python sample.py,
جایی که sample
نام برنامه است.
در تصویر بالا، من برنامه را در CMD اجرا می کنم روی حق برنامه بلافاصله خروجی هر دو تابع را چاپ کرد add()
و sub()
. این برنامه همچنان در حال اجرا است، زیرا ما استفاده کردیم time.sleep()
در هر دو تابع، که اجرای آن را متوقف می کند add_process
برای 10 ثانیه و sub_process
برای 20 ثانیه
با کنار گذاشتن خروجی، بیایید تمرکز خود را به Task Manager تغییر دهیم. اگر گسترش دهید Windows Command Processor
، پیدا خواهید کرد 3 فرآیندهای پایتون در حال اجرا هستند. بیایید بررسی کنیم که چرا به جای دو، سه فرآیند پایتون وجود دارد (add_process
و sub_process
).
از آنجایی که ما برنامه را از طریق CMD راه اندازی کردیم، برنامه پایتون شما از طریق پردازنده فرمان ویندوز یا CMD راه اندازی می شود و بنابراین در لیست CMD قرار می گیرد. ما قبلاً می دانیم که وقتی یک برنامه پایتون را اجرا می کنید، مفسر پایتون شما آن را شروع می کند Main Thread
، که اجرای برنامه را مدیریت می کند.
این موضوع اصلی به عنوان فهرست شده است یکی process. سپس، همانطور که در حال ایجاد هستیم دو فرآیندهای درون برنامه، آنها به عنوان دو فرآیند اضافی تحت پردازشگر فرمان ویندوز (همانطور که در تصویر بالا مشاهده می شود) فهرست شده اند.
توجه داشته باشید که هر دو فرآیند کاملا مستقل هستند و هیچ منبع مشترکی ندارند. اگر می خواهید چیزی را بین فرآیندها به اشتراک بگذارید، باید مکانیسم های خاصی مانند a را پیاده سازی کنید صف
برنامه نویسی آسنکرون چیست؟
برنامه نویسی ناهمزمان یک رویکرد خاص برای مدیریت وظایف است که شامل انتظار برای تکمیل عملیات خارجی است. این اجازه می دهد تا یک برنامه ادامه اجرا وظایف دیگر در حالی که در انتظار برای اینکه این عملیات ها تمام شوند، به جای مسدود کردن تا زمانی که کامل شوند.
توجه: اگر با کدنویسی چند رشته ای در پایتون آشنایی ندارید، می توانید به مثال-2 بروید.
مثال 1:
دوباره همان مثال، اما این بار توابع با استفاده از thread ها اجرا می شوند. add()
توسط thread-1
و sub()
توسط thread-2
. در زیر نسخه threading مثال آمده است:
import threading
import time
a = 25
b = 30
def add(a, b):
print("Inside add function\n")
print("Waiting for 20 seconds in add \n")
time.sleep(20)
print("Addition value:", a + b)
def sub(a, b):
print("Inside sub function\n")
print("Subtraction Value {}:".format(a - b))
print("Waiting for 10 seconds in sub\n")
time.sleep(10)
# Create threads for add and sub functions
add_thread = threading.Thread(target=add, args=(a, b))
sub_thread = threading.Thread(target=sub, args=(a, b))
# Start both threads
add_thread.start()
sub_thread.start()
# Wait for both threads to finish
add_thread.join()
sub_thread.join()
print("Complete")
ما ایجاد کردیم add_thread
و sub_thread
و توابع را تعیین کرد add
و sub
به عنوان هدف اگر این برنامه را اجرا کنید، می توانید خروجی زیر را ببینید:
بیایید خروجی را تجزیه کنیم:
- هر دو
add
وsub
توابع با استفاده از موضوعات جداگانه شروع می شوند (add_thread
وsub_thread
). - را
add
تابع “Inside add function” را چاپ می کند و سپس نشان می دهد که 20 ثانیه در انتظار استفاده استtime.sleep(20)
. - را
sub
تابع “Inside Sub function” و “Subtraction Value -5:” (نتیجه عملیات تفریق) را چاپ می کند و به دنبال آن پیامی مبنی بر اینکه 10 ثانیه با استفاده از آن منتظر است را چاپ می کند.time.sleep(10)
. - از آنجایی که رشته ها به طور همزمان در حال اجرا هستند، بسته به اینکه پیام های هر دو تابع ممکن است به صورت متقابل ظاهر شوند. روی ترتیب اجرای نخ ها
- پس از دوره های انتظار، هر دو رشته print نتایج عملیات مربوطه خود را. را
add
تابع “مقدار اضافی: 55” را چاپ می کند وsub
تابع هیچ چیز دیگری را چاپ نمی کند. - در نهایت، برنامه اصلی پس از اتمام اجرای هر دو رشته، “کامل” را چاپ می کند.
در حالی که add_thread()
منتظر 20 ثانیه است، شما sub_thread()
نتیجه را ارائه کرده و به مدت 10 ثانیه شروع به خوابیدن کرده است. در همین حال، add_thread
زمان انتظار خود را تکمیل کرد، مقدار اضافه را محاسبه کرد و نتیجه را چاپ کرد. بنابراین، در حالی که یک رشته در انتظار است، رشته دوم شما شروع به اجرا کرد و در حالی که رشته دوم شما منتظر بود، رشته اول شما شروع به اجرا کرد.
این روش مدیریت وظایف برای اجرای همزمان زمانی که یک کار در انتظار منابع خارجی است نامیده می شود. برنامه نویسی ناهمزمان
نکته کلیدی که باید به آن توجه داشت این است که رشته ها به طور همزمان اجرا می شوند و به برنامه اجازه می دهند وظایف را به صورت موازی انجام دهد. خروجی درون لایه ماهیت ناهمزمان threading را نشان می دهد، جایی که بخش های مختلف برنامه می توانند به طور همزمان اجرا شوند.
مثال 2:
بیایید دو کار را در نظر بگیریم: وظیفه 1 لیستی از کارکنان را از پایگاه داده یک شرکت بازیابی می کند و وظیفه 2 لیستی از پروژه های فعال توسط همان شرکت را بازیابی می کند. واضح است که این وظایف مرتبط یا وابسته نیستند روی یکدیگر – آنها وظایف مستقلی هستند.
بیایید چند کد نمونه برای این کارها بنویسیم:
def get_employees():
# connect to the database
# code to get employees list from database
return employees_list
def get_active_projects():
# connect to the database
# code to get the projects under development
return active_projects
# run task1
get_employees()
# run task2
get_active_projects()
نگران کد داخل توابع نباشید – ما می خواهیم مفهوم را در اینجا بفهمیم.
وقتی برنامه بالا را اجرا می کنید، مفسر پایتون به آن می گوید main thread
تا برنامه را مرحله به مرحله اجرا کنید، از بالا شروع کنید و به پایین بروید.
ابتدا task-1 را انجام می دهد که فرض کنید 2 ثانیه طول می کشد و سپس حرکت می کند روی به task-2، که فرض کنید 2 ثانیه طول می کشد. ما می دانیم که این وظایف مستقل هستند، پس چرا task-2 باید منتظر باشد تا task-1 تمام شود؟
اگر مکانیزمی وجود داشته باشد که بتوانم به مترجم بگویم تا task-1 را شروع کند (دریافت لیستی از کارمندان از پایگاه داده)، و در حالی که منتظر می ماند تا task-1 تکمیل شود، همچنین می توانم به او دستور دهم تا task-2 را شروع کند (دریافت یک لیست پروژه های فعال از پایگاه داده)، پس من اساساً هر دو کار را تقریباً همزمان اجرا می کنم. این به من کمک می کند تا میزان آن را کاهش دهم کل زمان اجرا از هر دو وظیفه این نمونه دیگری از برنامه نویسی ناهمزمان.
حالا معلوم است. چطور این کار را انجام دهیم؟ چگونه اطمینان حاصل کنیم که task-2 اجرا می شود در حالی که منتظر task-1 هستیم تا لیست کارمندان را از پایگاه داده دریافت کنیم؟ ما ابزارهای زیادی برای آن داریم. چند رشته ای یکی از آنهاست.
در مثال بالا، شما از دو رشته استفاده می کنید: thread-1 برای task-1 و سپس بلافاصله Thread-2 را برای task-2 شروع می کنید. Thread-2 نباید منتظر بماند تا Thread-1 تمام شود.
همچنین می توانید از مفاهیمی مانند همگام سازی/انتظار، پاسخ به تماس ها، و وعده می دهد در زبان های برنامه نویسی مختلف برای پیاده سازی برنامه نویسی ناهمزمان. در اینجا می توانید اطلاعات بیشتری در مورد این مفاهیم در جاوا اسکریپت بخوانید.
Concurrency چیست؟
بالاخره به همزمانی رسیدیم!
اکنون باید درک کنید که چند رشته یا چند پردازش شامل استفاده از چندین رشته و فرآیند برای انجام چندین کار به طور همزمان برای کاهش زمان لازم برای اجرای یک برنامه یا برنامه است.
اما چگونه این اتفاق می افتد؟ چه شده روی در پس زمینه؟ چگونه پردازنده اطمینان می دهد که رشته ها یا فرآیندها به طور همزمان اجرا می شوند؟
تصور کنید دو وظیفه دارید: رانندگی ماشین و برقراری تماس تلفنی. تصمیم می گیرید هر دو را یکجا انجام دهید. در حین رانندگی، با تلفن همراه خود با دوست خود تماس می گیرید و صحبت می کنید.
شما این وظایف را به طور همزمان انجام می دهید، اما یک جزئیات کوچک وجود دارد که مهم است: مغز شما به سرعت جاده را اسکن می کند، ماشین های دیگر را بررسی می کند و مطمئن می شود که متمرکز و ثابت هستید. در اینجا حدود یک میلیثانیه میگذرد، سپس به صحبت با دوستتان میرود، که ممکن است یک میلیثانیه دیگر طول بکشد، و سپس به رانندگی در هر میلیثانیه بازمیگردد. به طور مداوم بین هر دو کار سوئیچ می کند.
همانطور که زمان صرف شده است روی هر کار بسیار کوتاه است (فقط یک میلی ثانیه)، ممکن است فکر کنید که هر دو کار را همزمان انجام می دهید. اما یک تفاوت بسیار کوچک در زمان برای هر کار وجود دارد، که باعث می شود به نظر برسد هر دو کار به طور همزمان انجام می شوند.
به روشی مشابه، زمانی که دو کار توسط دو رشته یا دو پردازش انجام می شود، پردازنده شما خیلی سریع بین این وظایف سوئیچ می کند. thread-1 را به مدت 1 میلی ثانیه اجرا می کند، سپس حالت خود را ذخیره می کند و به thread-2 تغییر می کند و آن را برای 1 میلی ثانیه دیگر اجرا می کند. پس از ذخیره حالت thread-2، به thread-1 برمی گردد و آن را برای یک میلی ثانیه دیگر اجرا می کند.
من از 1 میلی ثانیه به عنوان مثال استفاده می کنم، اما در واقعیت، حتی سریعتر اتفاق می افتد. سرعت سوئیچینگ بستگی دارد روی پردازنده شما
از آنجایی که جابجایی بین کارها بسیار سریع است، این تصور را ایجاد می کند که هر دو کار به طور همزمان اجرا می شوند. اما توجه به این نکته مهم است که حتی اگر بخواهید هر دو thread-1 و thread-2 همزمان اجرا شوند، پردازنده و سیستم عامل شما تصمیم می گیرند که کدام یک را اول اولویت قرار دهند، چه مقدار زمان به هر کدام اختصاص دهند و به چه ترتیبی. تا آنها را اجرا کنند.
به طور خلاصه، این مانند شعبده بازی چند کار به طور همزمان است. شما یک کار را شروع میکنید، در صورت نیاز به کار دیگری تغییر میدهید، و تا زمانی که همه چیز تمام شود، از آنها عبور میکنید. این مفهوم نامیده می شود همزمانی.
همزمانی مفهومی برای مدیریت پیشرفت چندین کار به طور همزمان است، حتی اگر آنها به طور همزمان اجرا نشوند.
چطور به این میرسی؟ استفاده از Multi-threading و multi-processing دوباره!
شاید تا به حال متوجه شده باشید که Concurrency و Asynchronous Programming مفاهیم اصلی هستند، در حالی که multi-threading و multi-processing پیاده سازی این مفاهیم هستند.
قفل مترجم جهانی (GIL)
مفاهیم برنامه نویسی همزمان و ناهمزمان، صرف نظر از اینکه از چه زبان برنامه نویسی استفاده می کنید، یکسان هستند. اما اجرای این مفاهیم بستگی دارد روی زبان برنامه نویسی که انتخاب می کنید
وقتی صحبت از چند رشته می شود، پایتون کمی عجیب رفتار می کند. بیایید با یک مثال کوچک این را بفهمیم.
در دوران کودکی من و برادر کوچکترم بازی های رایانه ای انجام می دادیم. مادرم قانونی وضع کرد که برادرم باید اول 30 دقیقه بازی کند و بعد من می توانم 30 دقیقه دوم بازی کنم. این قانون برای اطمینان از این بود که هیچ کس با تلاش برای انجام کارها به طور همزمان، بازی را به هم نریزد. بنابراین من 30 دقیقه صبر می کردم تا نوبت من برسد تا 30 دقیقه بازی را انجام دهم.
در دنیای کامپیوتر، برنامه ها مانند من و برادرم هستند که سعی می کنیم این بازی را انجام دهیم. را قفل مترجم جهانی (GIL) مانند قاعدهای است که فقط به یک نفر، یا من یا برادرم (یا نخ) اجازه میدهد بازی را در یک زمان اجرا کنیم (یا بایت کد پایتون را اجرا کنیم).
GIL قانونی است که به تنها یک رشته در هر زمان اجازه می دهد تا بایت کد پایتون را اجرا کند. Global Interpreter Lock قفلی است که از دسترسی به اشیاء پایتون محافظت می کند و از اجرای همزمان بایت کد پایتون توسط چندین رشته در یک واحد جلوگیری می کند. process. این بدان معنی است که حتی در یک برنامه پایتون چند رشته ای، فقط یک رشته می تواند بایت کد پایتون را در هر زمان معین اجرا کند.
در نتیجه، این باعث محدودیت عملکرد چند رشته ای در می شود وظایف محدود به CPU. توجه داشته باشید که وظایف محدود به CPU کارهایی هستند که بسیار متکی هستند روی CPU به جای عملیات IO. محاسبات ریاضی، فشرده سازی و رفع فشرده سازی فایل ها و کامپایل کردن برنامه ها توسط یک کامپایلر برنامه چند نمونه از کارهای محدود به CPU هستند که از CPU بیشتری استفاده می کنند.
بیایید به یک مثال نگاه کنیم:
import time
def count_up():
count = 0
for i in range(100000000):
count = count + i
def count_down():
count = 0
for i in range(100000000):
count = count + i
if __name__ == "__main__":
start_time = time.time()
count_up()
count_down()
end_time = time.time()
print(f"Time taken: {end_time - start_time} seconds")
در برنامه فوق ما در حال اجرا هستیم count_up()
و count_down()
به صورت متوالی یکی پس از دیگری عمل می کند. را count_up
و count_down
هر یک از توابع را در یک محدوده بزرگ تکرار می کند و مجموع تجمعی اعداد را محاسبه می کند. خروجی برنامه این است:
Time taken: 25.86127805709839 seconds
بیایید همان برنامه را با استفاده از Multi-threading به صورت زیر بنویسیم:
import threading
import time
def count_up():
count = 0
for i in range(100000000):
count = count + i
def count_down():
count = 0
for i in range(100000000):
count = count + i
if __name__ == "__main__":
start_time = time.time()
# Create two threads, each running a CPU-bound task
thread1 = threading.Thread(target=count_up)
thread2 = threading.Thread(target=count_down)
# Start both threads
thread1.start()
thread2.start()
# Wait for both threads to finish
thread1.join()
thread2.join()
end_time = time.time()
print(f"Time taken: {end_time - start_time} seconds")
دو رشته thread1
و thread2
برای اجرا ایجاد می شوند count_up
و count_down
به طور همزمان عمل می کند. این برنامه زمان لازم برای تکمیل هر دو رشته را با استفاده از time
مدول. خروجی برنامه این است:
Time taken: 24.498752117156982 seconds
توجه داشته باشید که در رایانه شخصی شما، زمان صرف شده برای تکمیل این برنامه می تواند متفاوت باشد. اگر مشاهده کنید، خروجی برای sequential
و multi-threading
نسخه های برنامه (25.8 seconds vs 24.98 seconds)
تفاوت زیادی بین زمان صرف شده وجود ندارد. این به دلیل GIL است.
قفل مفسر جهانی (GIL) در CPython، که به تنها یک رشته اجازه می دهد تا بایت کد پایتون را در یک زمان اجرا کند، به این معنی است که زمان اجرا نمی خواهد در مقایسه با اجرای متوالی وظایف، پیشرفت قابل توجهی نشان می دهد.
این محدودیت چند رشته ای در پایتون را برای وظایف محدود به CPU به دلیل GIL برجسته می کند.
اما شایان ذکر است که در حالی که GIL از اجرای همزمان بایت کد پایتون توسط چندین رشته جلوگیری می کند، به طور کلی از threading جلوگیری نمی کند. رشتههای پایتون همچنان میتوانند برای کارهای I/O-Bound که در آن رشتهها بیشتر وقت خود را در انتظار عملیات خارجی (مانند شبکه یا ورودی/خروجی دیسک) به جای انجام محاسبات فشرده CPU میگذرانند، مفید باشند.
بنابراین چگونه محدودیت های GIL را حذف می کنید؟
از طعم دیگری از پایتون استفاده کنید
پیاده سازی استاندارد پایتون Cpython است. این پایتون است که با استفاده از زبان C طراحی شده است و بیشتر در سراسر جهان استفاده می شود. برای جلوگیری از GIL، میتوانیم از Jython (پایتون توسعهیافته با استفاده از جاوا)، IronPython (پایتون توسعهیافته با استفاده از داتنت) یا PyPy (پایتون توسعهیافته با پایتون) استفاده کنیم.
برای اطلاعات بیشتر می توانید منابع زیر را بررسی کنید:
- صفحه اصلی | جیتون
- IronPython.net
- PyPy
از ماژول multiprocessing استفاده کنید
Multiprocessing ماژولی است که به شما کمک می کند از چندین هسته CPU خود برای اجرای برنامه خود در فرآیندهای جداگانه استفاده کنید.
هر یک process هسته CPU، حافظه، منابع و interpreter
. بله، هر process مفسر خود را برای اجرای کد پایتون شما دارد. اگر 4 هسته CPU دارید، می توانید 4 پردازش را اجرا کنید که هر کدام مفسر خاص خود را دارند که برنامه شما را در حافظه خود اجرا می کند.
بیایید برنامه ای را که در آن بحث کردیم بنویسیم GIL مفهوم با استفاده از پردازش چندگانه به شرح زیر است:
import multiprocessing
import time
def count_up():
count = 0
for i in range(100000000):
count = count + i
def count_down():
count = 0
for i in range(100000000):
count = count + i
if __name__ == "__main__":
start_time = time.time()
# Create two threads, each running a CPU-bound task
process1 = multiprocessing.Process(target=count_up)
process2 = multiprocessing.Process(target=count_down)
# Start both threads
process1.start()
process2.start()
# Wait for both threads to finish
process1.join()
process2.join()
end_time = time.time()
print(f"Time taken: {end_time - start_time} seconds")
خروجی برنامه زمانی که آن را از طریق خود اجرا می کنید terminal یا CMD عبارت است از:
Time taken: 8.376060724258423 seconds
این نشان دهنده بهبود قابل توجهی در زمان صرف شده در مقایسه با تکنیک هایی مانند Sequential و Multi-threading است (25.8 در مقابل 24.4 در مقابل 8.3 ثانیه). این نشان می دهد که چگونه استفاده از چند پردازش می تواند به کاهش زمان CPU شما کمک کند.
توجه داشته باشید که اگر بخواهید بین فرآیندها ارتباط برقرار کنید، سربار وجود خواهد داشت. در نتیجه، در حالی که پردازش چندگانه می تواند برای وظایف محدود به CPU موثر باشد، ممکن است برای همه سناریوها مناسب نباشد، به ویژه آنهایی که شامل ارتباطات مکرر یا انتقال داده های بزرگ بین فرآیندها هستند.
اجرای برنامه نویسی ناهمزمان!
اگر برنامه یا برنامه شما باند CPU سنگین نیست یا بیشتر وابسته است روی پردازش CPU سپس با استفاده از asyncio
ماژول پایتون، می توانید مفهوم برنامه نویسی ناهمزمان را پیاده سازی کنید (منتظر تکمیل کار 1 قبل از شروع کار 2 نباشید).
این ماژول عمدتا در عملیات I/O استفاده می شود. Asyncio به شما امکان می دهد کدهای ناهمزمان بنویسید که به طور مشترک چند کار را انجام می دهد بدون با تکیه روی رشتهها، که میتوانند در موقعیتهایی که در غیر این صورت رشتهها در انتظار تکمیل عملیات I/O مسدود میشوند، کمک کنند.
با استفاده از asyncio، میتوانید کد غیرمسدود کننده بنویسید که به سایر وظایف اجازه میدهد در حالی که منتظر پایان عملیات ورودی/خروجی هستند، اجرا شوند، بنابراین به طور بالقوه همزمانی و پاسخگویی کلی برنامه شما را بهبود میبخشد.
توجه به این نکته مهم است که asyncio خود GIL را حذف نمی کند – بلکه یک مدل همزمانی جایگزین ارائه می دهد که می تواند برای انواع خاصی از برنامه ها کارآمدتر باشد.
برای وظایف محدود به CPU، asyncio ممکن است مزایای عملکردی مشابه روش های چند پردازشی یا دیگر تکنیک های موازی را ارائه نکند، زیرا هنوز در محدودیت های GIL عمل می کند. در چنین مواردی، چند پردازش یا سایر رویکردهای همزمان ممکن است مناسب تر باشند.
خلاصه
در این مقاله با تفاوت های Multi-threading، Multi-processing، Asynchronous Programming و Concurrency آشنا شدید. اکنون به طور خلاصه آنها را مرور می کنیم:
چند رشته ای:
- تعریف: Multi-threading شامل استفاده از چندین رشته برای اجرای وظایف است همزمان در یک واحد process. همزمان چیزی جز مفهوم Concurrency در اینجا نیست.
- شفاف سازی: رشتهها فضای حافظه یکسانی را به اشتراک میگذارند و به آنها امکان میدهد به راحتی با هم ارتباط برقرار کنند، اما برای جلوگیری از تضاد نیاز به همگامسازی دقیق دارند.
پردازش چندگانه:
- تعریف: چند پردازش شامل استفاده از چندین فرآیند برای اجرای برنامه یا وظایف شما است. هر یک process فضای حافظه و منابع خاص خود را دارد.
- شفاف سازی: فرآیندها مستقل هستند و ارتباط بین آنها اغلب نیازمندprocess مکانیسم های ارتباطی (IPC) هر یک process در فضای حافظه خود عمل می کند.
برنامه نویسی ناهمزمان:
- تعریف: برنامه نویسی ناهمزمان یک مفهوم برنامه نویسی است که به کارها اجازه می دهد جدا از جریان اصلی برنامه اجرا شوند. لزوماً به معنای اجرای همزمان نیست.
- شفاف سازی: وظایف ناهمزمان برنامه اصلی را مسدود نمیکند و برنامه را قادر میسازد تا در حالی که منتظر تکمیل وظایف ناهمزمان است، به اجرای خود ادامه دهد. این معمولاً در عملیات I/O برای بهبود کارایی استفاده می شود.
همزمانی:
- تعریف: همزمانی مفهوم گستردهتری است که به توانایی یک سیستم برای اجرای چندین کار در بازههای زمانی همپوشانی اشاره دارد، یعنی جابهجایی بین وظایف در بازههای زمانی مشخص.
- شفاف سازی: همزمانی اجرای همزمان را تضمین نمی کند، بلکه تمرکز دارد روی مدیریت کارآمد چندین کار با جابجایی بین آنها. این شامل برنامه نویسی چند رشته ای، چند پردازشی و ناهمزمان به عنوان راه هایی برای دستیابی به اجرای همزمان است.
امیدوارم اکنون درک روشنی از مفاهیمی داشته باشید که ممکن است قبلاً ترسناک به نظر می رسیدند. میدانم که بیشتر جنبه تئوری دارد، اما میخواهم مطمئن شوم که قبل از وارد شدن به جنبه عملی، مفهوم را به خوبی درک کردهاید.
تا آن زمان، دوست شما در اینجا، Hemachandra، در حال امضای قرارداد است…
برای دوره های بیشتر می توانید به وب سایت شخصی من مراجعه کنید.
روز خوبی داشته باشی!
منتشر شده در 1403-02-21 02:33:05