مقدمه
اگر در حوزه توسعه نرمافزارهای مدرن فعالیت دارید، میدانید که برنامهها باید بتوانند چندین کار را بهصورت کارآمد و با پاسخگویی بالا انجام دهند. در این میان، پایتون زبانی قدرتمند و پرکاربرد، با قابلیتهایی که ارائه میدهد به زبانی محبوب تبدیل شده است. یکی از این قابلیتها ماژول Threading است که اجرای همزمان وظایف در یک فرآیند واحد را ممکن میسازد. این قابلیت بهویژه برای وظایف محدود شده توسط ورودی/خروجی (I/O-bound)، مانند درخواستهای شبکه یا عملیات فایل، بسیار ارزشمند است. پیش از این به بررسی نخها در زبان جاوا در این مقاله پرداختیم اکنون با ما همراه باشید تا در این مقاله به بررسی دقیقتر Threading در پایتون بپردازیم.
نخها در پایتون
- امکان اجرای همزمان وظایف محدود شده توسط ورودی/خروجی، مانند دانلود فایل یا پرسوجو از پایگاه داده.
- بهبود استفاده از منابع با فعال نگهداشتن پردازنده در زمان انتظار برای عملیات ورودی/خروجی.
- سادهسازی طراحی برنامه برای وظایفی که بهطور طبیعی بهصورت موازی اجرا میشوند، مانند مدیریت درخواستهای متعدد کاربران در یک سرور وب.
مفاهیم مورد نیاز چندنخی
-
نخ در مقابل فرآیند (Thread vs Process):
- قفل مفسر سراسری (Global Interpreter Lock - GIL)
- همزمانی (Concurrency) در مقابل موازیسازی (Parallelism):
- چند-نخی (Multi-Threading) در مقابل چند-فرآیندی (Multi-Processing)
-
چندنخی در مقابل برنامهنویسی ناهمگام (Asynchronous Programming)
استفاده از نخها
این مثال یک کلاس نخ ایجاد میکند که نام نخ را پس از تأخیر تصادفی چاپ میکند:
در این مثال ما ماژول random پایتون، ماژول time و کلاس Thread از ماژول threading را وارد میکنیم. سپس کلاس Thread را زیرکلاسگیری کرده و متد __init__ آن را بازنویسی میکنیم تا یک آرگومان با برچسب nae بپذیرد.
برای شروع یک نخ، باید متد start آن را فراخوانی کنید. وقتی یک نخ را شروع میکنید، پایتون بهطور خودکار متد run نخ را اجرا میکند. ما متد run را بازنویسی کردهایم تا نخ یک مقدار تصادفی زمان برای وقفه انتخاب کند.
تابع random.randint در اینجا باعث میشود پایتون بهطور تصادفی عددی بین ۳ تا ۱۵ را انتخاب کند. سپس نخ را به همان تعداد ثانیه که انتخاب شده، به خواب میبریم تا شبیهسازی کنیم که نخ در حال انجام کاری است. در نهایت، نام نخ را چاپ میکنیم تا کاربر بداند که نخ کار خود را به پایان رسانده است.
import random
import time
from threading import Thread
class MyThread(Thread):
def __init__(self, name):
Thread.__init__(self)
self.name = name
def run(self):
amount = random.randint(3, 15)
time.sleep(amount)
print(f"Thread {self.name} is running")
def create_threads():
for i in range(5):
name = f"Thread #{i+1}"
my_thread = MyThread(name)
my_thread.start()
if __name__ == "__main__":
create_threads()
import os
import urllib.request
from threading import Thread
class DownloadThread(Thread):
def __init__(self, url, name):
Thread.__init__(self)
self.name = name
self.url = url
def run(self):
handle = urllib.request.urlopen(self.url)
fname = os.path.basename(self.url)
with open(fname, "wb") as f_handler:
while True:
chunk = handle.read(1024)
if not chunk:
break
f_handler.write(chunk)
print(f"{self.name} has finished downloading {self.url}!")
def main(urls):
for item, url in enumerate(urls):
name = f"Thread {item+1}"
thread = DownloadThread(url, name)
thread.start()
if __name__ == "__main__":
urls = [
"http://www.irs.gov/pub/irs-pdf/f1040.pdf",
"http://www.irs.gov/pub/irs-pdf/f1040a.pdf",
"http://www.irs.gov/pub/irs-pdf/f1040ez.pdf",
"http://www.irs.gov/pub/irs-pdf/f1040es.pdf",
"http://www.irs.gov/pub/irs-pdf/f1040sb.pdf"
]
main(urls)
همگامسازی نخها
import threading
counter = 0
lock = threading.Lock()
def increment():
global counter
for _ in range(100000):
with lock:
counter += 1
threads = [threading.Thread(target=increment) for _ in range(2)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
print(f"Final counter value: {counter}")
استفاده از صفها برای ارتباط ایمن بین نخها
ماژول queue کلاس Queue را ارائه میدهد که برای هماهنگی وظایف بین نخها ایمن است و از انواع روشهای FIFO، LIFO و اولویتدار پشتیبانی میکند. مثال زیر یک دانلودکننده فایل با استفاده از صف را نشان میدهد در این کد یک Queue برای نگهداری URLها ایجاد میشود. پنج نخ پسزمینه (daemon) ایجاد شده و URLها را از صف میگیرند. متدهای put، get و task_done برای مدیریت وظایف استفاده میشوند. متد queue.join منتظر تکمیل تمام وظایف میماند.
import os
import threading
import urllib.request
from queue import Queue
class Downloader(threading.Thread):
def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue
def run(self):
while True:
url = self.queue.get()
self.download_file(url)
self.queue.task_done()
def download_file(self, url):
handle = urllib.request.urlopen(url)
fname = os.path.basename(url)
with open(fname, "wb") as f:
while True:
chunk = handle.read(1024)
if not chunk:
break
f.write(chunk)
print(f"Finished downloading {url}")
def main(urls):
queue = Queue()
for i in range(5):
t = Downloader(queue)
t.setDaemon(True)
t.start()
for url in urls:
queue.put(url)
queue.join()
if __name__ == "__main__":
urls = [
"http://www.irs.gov/pub/irs-pdf/f1040.pdf",
"http://www.irs.gov/pub/irs-pdf/f1040a.pdf",
"http://www.irs.gov/pub/irs-pdf/f1040ez.pdf",
"http://www.irs.gov/pub/irs-pdf/f1040es.pdf",
"http://www.irs.gov/pub/irs-pdf/f1040sb.pdf"
]
main(urls)
استخر نخها
ماژول concurrent.futures کلاس ThreadPoolExecutor را برای مدیریت استخر نخها ارائه میدهد که سربار ایجاد نخ را کاهش میدهد:
from concurrent.futures import ThreadPoolExecutor
import time
def perform_task(task_id):
print(f"Task {task_id} started")
time.sleep(2)
result = task_id * 2
print(f"Task {task_id} completed with result: {result}")
return result
with ThreadPoolExecutor(max_workers=3) as executor:
tasks = [1, 2, 3, 4, 5]
results = executor.map(perform_task, tasks)
for result in results:
print(f"Result: {result}")
جمعبندی
چندنخی در پایتون ابزاری قدرتمند برای دستیابی به همزمانی در برنامههای I/O-bound است. با درک نحوه ایجاد نخها، همگامسازی منابع مشترک، و استفاده از ابزارهایی مانند صفها میتوانید برنامههایی پاسخگو و کارآمد بسازید. اگرچه GIL چندنخی را برای وظایف CPU-bound محدود میکند، اما برای وظایف شامل انتظار، مانند عملیات شبکه یا فایل، ایدهآل است. با مثالها و دانش ارائهشده، آماده هستید تا چندنخی را بهطور مؤثر در پروژههای خود پیادهسازی کنید.
سوالات متداول
-
نخهای پسزمینه (Daemon) چه کاربردی دارند؟
- چگونه از شرایط رقابتی در چندنخی جلوگیری کنیم؟
- ThreadPoolExecutor چه مزیتی دارد؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
ثبت نظر