برنامهنویسی چند وظیفهای یکی از اصولیترین ویژگیهای برنامهنویسی مدرن است. همزمانی به برنامهنویسان این امکان را میدهد تا وظایف مختلفی را همزمان اجرا کرده و عملکرد برنامهها را بهبود بخشند. در این مقاله، ما به بررسی همزمانی در پایتون میپردازیم و نحوه استفاده از آن برای ساخت برنامههای همزمان، مورد بررسی قرار میدهیم.
چرا نیاز به همزمانی داریم؟
وقتی که یک برنامه در حال اجرا است، معمولاً باید چندین وظیفه را انجام دهد. این وظایف ممکن است به صورت همزمان و موازی اجرا شوند یا به صورت ترتیبی اجرا شوند. در صورتی که یک برنامه تنها از یک Thread استفاده کند و وظایف به ترتیب اجرا شوند، ممکن است عملکرد برنامه کند شود و زمان اجرا طولانی شود.
همزمانی به برنامهنویسان این امکان را میدهد تا چندین Thread به صورت موازی اجرا کنند و وظایف مختلفی را به طور همزمان انجام دهند. این مفهوم به خصوص برای برنامههایی که وظایف مستقلی دارند و از منابع مختلفی مانند ورودی-خروجی، پردازشهای محاسباتی و ارتباط با شبکه استفاده میکنند، بسیار حیاتی است. به عنوان مثال، در برنامههای گرافیکی، میتوان همزمان تعداد زیادی از Threadها برای بهبود برنامه استفاده کرد.
Thread و همزمانی در پایتون
در پایتون، ترد (Thread) یک جریان اجرایی مستقل از برنامه اصلی است که به صورت همزمان اجرا میشود. پایتون از ماژول threading
برای ایجاد و مدیریت Threadها استفاده میکند. هر برنامه پایتون حداقل یک thread اصلی دارد که در آن اجرای برنامه شروع میشود. Threadهای اضافی میتوانند توسط برنامهنویس ایجاد شوند و ماموریتهای مختلفی را انجام دهند.
ایجاد Thread
برای ایجاد یک Thread در پایتون، باید از کلاس Thread
در ماژول threading
استفاده کنید. در زیر یک مثال ساده از ایجاد thread نمایش داده شده است:
import threading
def print_numbers():
for i in range(1, 6):
print(f"Number: {i}")
def print_letters():
for letter in 'abcde':
print(f"Letter: {letter}")
# ایجاد تردها
t1 = threading.Thread(target=print_numbers)
t2 = threading.Thread(target=print_letters)
# شروع اجرای تردها
t1.start()
t2.start()
در این مثال، ما دو تابع به نام print_numbers
و print_letters
تعریف کردهایم. سپس با استفاده از کلاس Thread
، دو thread به نام t1
و t2
ایجاد میکنیم و تابع مرتبط با هر Thread را به عنوان هدف اجرایی (target) آن Thread مشخص میکنیم. در انتها با فراخوانی متد start
، اجرای Threadها آغاز میشود. در خروجی دو تابع به صورت همزمان اجرا خواهند شد.
خروجی:
Number: 1
Number: 2
Letter: a
Letter: b
Number: 3
Letter: c
Number: 4
Letter: d
Number: 5
Letter: e
همزمانی و همگامی
یکی از مفاهیم مهم Multithreading، تفاوت بین همزمانی (Concurrency) و همگامی (Parallelism) است.
- همزمانی (Concurrency): وقتی میگوییم دو یا چند وظیفه همزمان اجرا میشوند، منظورمان همزمانی است. Threadها میتوانند به صورت همزمان اجرا شوند، اما این اجرا ممکن است روی یک پردازنده و به صورت موازی نباشد.
- همگامی (Parallelism): وقتی میگوییم دو یا چند وظیفه به صورت همگامی اجرا میشوند، منظورمان اجرای همزمان و موازی روی چندین پردازنده و یا هسته است. این اجرا به طور واقعی همزمان و موازی است و منجر به افزایش سرعت اجرای برنامه میشود.
پایتون برای همزمانی (Concurrency) معمولاً Thread را پیشنهاد میکند اما برای همگامی (Parallelism) از multiprocessing استفاده میشود که در آموزشهای دیگر به این مبحث خواهیم پرداخت.
همزمانی در پایتون
در پایتون، مدیریت Threadها به کمک ماژول threading
انجام میشود. این ماژول امکانات زیادی برای کنترل threadها فراهم میکند. برخی از مهمترین متدها و ویژگیهای ماژول threading
عبارتند از:
1. ایجاد Thread
برای ایجاد یک thread جدید، از کلاس Thread
استفاده کنید و تابع مرتبط با thread را به عنوان هدف اجرایی آن مشخص کنید.
import threading
def my_function():
# کد ترد
pass
# ایجاد ترد
my_thread = threading.Thread(target=my_function)
2. شروع Thread
با فراخوانی متد start
، ترد شما شروع به اجرا میکند. این متد به thread اجازه میدهد که به صورت موازی با Threadهای دیگر اجرا شود.
my_thread.start()
3. انتظار برای اتمام Thread
برای انتظار تا اتمام threadها، میتوانید از متد join
استفاده کنید.
my_thread.join()
4. تغییر اولویت Thread
میتوانید اولویت Threadها را با استفاده از متد setDaemon
تعیین کنید. threadهای با اولویت بالا اولویت بیشتری برای اجرا دارند.
my_thread.setDaemon(True)
5. مدیریت Threadها با Lock
از Lockها برای مدیریت دسترسی همزمان به متغیرها و منابع در threadها استفاده میشود.
my_lock = threading.Lock()
# قفل بگذار
my_lock.acquire()
# قفل گشا
my_lock.release()
6. Thread در حال اجرا
متدهایی مانند current_thread()
به شما امکان میدهند تا Threadی که در حال اجرا در آن هستید را تشخیص دهید.
current_thread = threading.current_thread()
print(current_thread.name)
مزایا و معایب Multithreading در پایتون
مزایا:
- این اجازه را میدهد تا وظایف مختلف به صورت همزمان اجرا شوند: با استفاده از Threadها، میتوانید وظایف مختلفی را به صورت همزمان اجرا کنید و زمان اجرای برنامه را بهبود بخشید.
- استفاده بهینه از منابع: Threadها به صورت هوشمند منابع را بهینه میکنند. اگر یک Thread در حالت انتظار باشد، پردازنده به Thread دیگری میپردازد. این منجر به بهرهوری بالاتری در استفاده از پردازنده میشود.
معایب:
- پیچیدگی: مدیریت Threadها و همگامسازی بین آنها پیچیده میشود. اشتباهات در مدیریت Threadها میتواند به مشکلاتی از جمله “deadlock” منجر شود.
- مصرف حافظه اضافی: هر Thread از منابع حافظه اضافی برای مدیریت استفاده میکند. بنابراین، برنامههایی که تعداد زیادی Thread دارند، ممکن است مصرف حافظه زیادی داشته باشند.
مثال عملی: پردازش تصاویر با Multithreading
برای نمایش کاربرد عملی Multithreading در پایتون، ما یک مثال پردازش تصاویر را بررسی خواهیم کرد. در این مثال، ما تصاویر را از یک پوشه میخوانیم، آنها را پردازش میکنیم و نتیجه را در پوشه دیگری ذخیره میکنیم. برای این کار، از Multithreading برای پردازش همزمان تصاویر استفاده میکنیم.
import os
import threading
import cv2
# تابعی برای پردازش تصاویر
def process_image(input_path, output_path):
# خواندن تصویر
image = cv2.imread(input_path)
# انجام پردازش (برای مثال، تبدیل تصویر به سیاه و سفید)
processed_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# ذخیره تصویر پردازش شده
cv2.imwrite(output_path, processed_image)
# پوشه ورودی و خروجی
input_folder = "input_images"
output_folder = "output_images"
# اطلاعات تمام فایلهای موجود در پوشه ورودی
input_files = os.listdir(input_folder)
# لیستی برای نگهداری تردها
threads = []
# ایجاد تردها برای پردازش هر تصویر
for file in input_files:
input_path = os.path.join(input_folder, file)
output_path = os.path.join(output_folder, file)
thread = threading.Thread(target=process_image, args=(input_path, output_path))
threads.append(thread)
thread.start()
# انتظار تا تمام تردها به اتمام برسند
for thread in threads:
thread.join()
print("تمام تصاویر پردازش شدند.")
در این مثال، از کتابخانه OpenCV برای پردازش تصاویر استفاده گردیده است. تعداد Threadها برابر با تعداد تصاویر ورودی است. هر Thread به تصویری از پوشه ورودی دسترسی دارد و پس از پردازش، تصویر پردازش گردیده را در پوشه خروجی ذخیره میکند. به این ترتیب، تمام تصاویر همزمان پردازش میشوند و زمان اجرای برنامه بهبود مییابد.