نخ (Thread) در پایتون: راهنمای جامع برنامه‌نویسی موازی

بفرست برای دوستت
Telegram
WhatsApp
چندنخی در پایتون

فهرست مطالب

برنامه‌نویسی چندنخی (Multithreading)  یا نخ در پایتون یکی از مفاهیم کلیدی در بهینه‌سازی عملکرد نرم‌افزارهاست. بسیاری از توسعه‌دهندگان پایتون در مسیر یادگیری خود به جایی می‌رسند که نیاز دارند وظایف مختلف را به‌طور همزمان اجرا کنند. برای مثال، فرض کنید می‌خواهید هم‌زمان چند درخواست وب ارسال کنید، یا چند فایل بزرگ را پردازش کنید، یا حتی در یک پروژه تحلیل داده با پایتون نیاز به اجرای همزمان چندین وظیفه دارید.

اینجاست که نخ (Thread) به کمک شما می‌آید. نخ‌ها این امکان را می‌دهند که چندین بخش از یک برنامه به‌طور موازی روی یک پردازنده اجرا شوند. اما همان‌طور که خواهیم دید، پایتون به دلیل وجود GIL (Global Interpreter Lock) محدودیت‌هایی دارد که درک آن برای استفاده صحیح از نخ‌ها ضروری است.

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

جدول مقایسه‌ای از ماژول‌های چندنخی در پایتون

ویژگی‌ها

threadingconcurrent.futuresmultiprocessing
سطح کنترلپایین (دستی)بالا (مدیریت‌شده)

بالا (پردازش جداگانه)

مدیریت منابع

سادهخودکارسنگین‌تر (حافظه بیشتر)
مناسب برایI/O-BoundI/O-Bound

CPU-Bound

استفاده از GIL

بلهبلهخیر
سهولت پیاده‌سازیمتوسطبالا

متوسط

انتخاب بین این ابزارها بستگی به نیاز پروژه دارد. اگر پروژه‌ی شما شامل وظایف شبکه‌ای و ورودی/خروجی باشد، threading یا concurrent.futures بهترین گزینه است. اما اگر با تحلیل داده‌های سنگین یا محاسبات پیچیده در پروژه‌هایی مثل دوره جامع متخصص علم داده سروکار دارید، multiprocessing انتخاب مناسب‌تری خواهد بود.

نخ (Thread) چیست و چرا در پایتون مهم است؟

نخ (Thread) چیست و چرا در پایتون مهم است؟

نخ (Thread) کوچک‌ترین واحد پردازشی در یک برنامه است که می‌تواند به‌طور مستقل اجرا شود. در واقع، وقتی یک برنامه را اجرا می‌کنید، به‌صورت پیش‌فرض تنها یک نخ اصلی (Main Thread) دارد. اما شما می‌توانید چندین نخ دیگر ایجاد کنید تا وظایف مختلف برنامه هم‌زمان انجام شوند.

اگر سوالی دارید یا نیاز به راهنمایی در مسیر یادگیری پایتون دارید، تیم دیتایاد آماده است تا شما را همراهی کند. برای مشاوره رایگان، با شماره  ۰۹۹۰۵۵۰۱۹۹۸     تماس بگیرید و شروع کنید!

چرا نخ در پایتون اهمیت دارد؟

در بسیاری از پروژه‌های واقعی، نیاز به انجام چند کار به‌طور همزمان وجود دارد. به‌عنوان مثال:

  • دانلود چندین فایل از اینترنت
  • پردازش داده‌های حجیم (مثل داده‌های سنسورها یا تحلیل داده با پایتون)
  • اجرای عملیات I/O مثل خواندن و نوشتن فایل‌ها یا کار با دیتابیس
  • ساخت برنامه‌های چت، ربات و اپلیکیشن‌های همزمان

اینجاست که نخ‌ها می‌توانند سرعت پاسخ‌دهی برنامه را افزایش دهند. البته باید توجه داشت که پایتون دارای یک قفل داخلی به نام GIL (Global Interpreter Lock) است. این قفل اجازه نمی‌دهد چند نخ همزمان روی چند هسته پردازنده کدهای CPU-Bound را اجرا کنند. اما برای کارهای I/O-Bound (مثل خواندن فایل یا کار با شبکه)، نخ‌ها فوق‌العاده مؤثر هستند.

تفاوت Thread و Process در پایتون

تفاوت Thread و Process در پایتون

یکی از پرسش‌های متداول در میان برنامه‌نویسان تازه‌کار این است که: نخ درپایتون چه تفاوتی با پردازش (Process) دارد؟

  • Process پردازش:
    • هر پردازش، یک فضای حافظه مستقل دارد.
    • ایجاد پردازش جدید حافظه و منابع بیشتری مصرف می‌کند.
    • برای کارهای سنگین CPU-Bound (مثل پردازش تصویر یا محاسبات ریاضی پیچیده و حتی پروژه‌های مرتبط با آموزش ریاضیات هوش مصنوعی) مناسب‌تر است.
  • Thread نخ:
    • نخ‌ها در یک پردازش مشترک اجرا می‌شوند و حافظه یکسانی دارند.
    • ایجاد نخ سبک‌تر از ایجاد پردازش است.
    • برای کارهای سبک‌تر و I/O-Bound مثل دانلود داده‌ها، خواندن فایل یا تعامل با API کاربرد بیشتری دارند.

به زبان ساده، Process مثل چندین کامپیوتر مستقل است، در حالی که Thread مثل چند کارگر داخل یک کامپیوتر واحد عمل می‌کند.

اگر تازه با پایتون شروع کرده‌اید و می‌خواهید با مفاهیم پایه‌ای زبان آشنا شوید، پیشنهاد می‌کنیم مقاله شی‌گرایی در پایتون را مطالعه کنید تا درک بهتری از ساختار برنامه‌نویسی داشته باشید. انواع پیاده‌سازی نخ در پایتون

پایتون برای مدیریت چندنخی ابزارهای مختلفی ارائه می‌دهد. انتخاب بین این ابزارها به سطح پیچیدگی پروژه، نوع وظایف (I/O-Bound یا CPU-Bound) و نیازمندی‌های عملکردی بستگی دارد. در ادامه مهم‌ترین گزینه‌ها را بررسی می‌کنیم:

نخ در پایتون

انواع پیاده‌سازی نخ در پایتون

ماژول threading پایه‌ای

ماژول threading یکی از رایج‌ترین راه‌ها برای کار با نخ در پایتون است. این ماژول به شما اجازه می‌دهد نخ‌های جدید ایجاد کرده و رفتار آن‌ها را کنترل کنید.

مثال ساده ایجاد یک نخ:

import threading




def print_numbers():

    for i in range(5):

        print(f"عدد: {i}")




# ایجاد نخ

t = threading.Thread(target=print_numbers)

t.start()




# ادامه اجرای نخ اصلی

print("نخ اصلی در حال اجرا است")

در این مثال، تابع print_numbers در یک نخ جداگانه اجرا می‌شود.

مزایا:

  • ساده و قابل فهم
  • مناسب برای کارهای I/O-Bound
  • کنترل مستقیم روی نخ‌ها

معایب:

  • نیاز به مدیریت دستی مشکلاتی مثل Race Condition و Deadlock

دوره پایتون

ماژول concurrent.futures پیشرفته

این ماژول یک رابط سطح بالاتر نسبت به threading ارائه می‌دهد و مدیریت نخ‌ها را آسان‌تر می‌کند. پرکاربردترین بخش آن ThreadPoolExecutor است.

مثال:

from concurrent.futures import ThreadPoolExecutor




def square(n):

    return n * n




with ThreadPoolExecutor(max_workers=3) as executor:

    results = executor.map(square, [1, 2, 3, 4, 5])

    print(list(results))

ویژگی‌ها:

  • مدیریت خودکار نخ‌ها
  • استفاده از Thread Pooling
  • نوشتن کد ساده‌تر و خواناتر نسبت به threading

مقایسه با multiprocessing

ماژول multiprocessing برخلاف threading، پردازش‌های جداگانه ایجاد می‌کند. بنابراین محدودیت GIL در آن وجود ندارد.

  • threading: مناسب برای وظایف I/O-Bound
  • multiprocessing: مناسب برای وظایف CPU-Bound مثل محاسبات ریاضی سنگین یا پروژه‌های مرتبط با پایتون یا C# که نیاز به کارایی بالا دارند.

به نقل از سایت geeksforgeeks:

«یک نخ، موجودیتی درون یک فرآیند است که می‌تواند برای اجرا زمان‌بندی شود. همچنین، کوچکترین واحد پردازشی است که می‌تواند در یک سیستم عامل (OS) انجام شود. به عبارت ساده، یک نخ، دنباله ای از چنین دستورالعمل‌هایی درون یک برنامه است که می‌تواند مستقل از سایر کدها اجرا شود.»

پیاده‌سازی گام به گام

پیاده‌سازی گام به گام

بعد از آشنایی با مفاهیم پایه‌ای نخ در پایتون، وقت آن رسیده که به سراغ پیاده‌سازی عملی برویم. در این بخش، از ساده‌ترین مثال‌ها تا مدیریت مشکلات پیچیده‌تر چندنخی را مرور می‌کنیم.

ایجاد نخ ساده با threading.Thread

برای شروع، کافی است یک تابع تعریف کنید و آن را درون یک نخ اجرا کنید.

import threading

import time




def worker():

    for i in range(3):

        print(f"کارگر در حال پردازش {i}")

        time.sleep(1)




# ایجاد نخ

t = threading.Thread(target=worker)

t.start()




print("این پیام از نخ اصلی است")

در این کد:

  • نخ اصلی همچنان اجرا می‌شود.
  • تابع worker در نخ جداگانه پردازش خود را انجام می‌دهد.

مدیریت Race Condition با Lock

زمانی که چند نخ به‌طور همزمان به یک منبع مشترک (مثل فایل یا متغیر) دسترسی داشته باشند، خطر Race Condition پیش می‌آید. برای جلوگیری از این مشکل از Lock استفاده می‌کنیم.

import threading




counter = 0

lock = threading.Lock()




def increment():

    global counter

    for _ in range(100000):

        with lock:   # استفاده از Lock

            counter += 1




threads = []

for _ in range(5):

    t = threading.Thread(target=increment)

    threads.append(t)

    t.start()




for t in threads:

    t.join()




print("مقدار نهایی:", counter)

بدون استفاده از Lock، مقدار نهایی counter نادرست خواهد بود چون نخ‌ها همزمان به آن دسترسی پیدا می‌کنند.

دوره جامع پایتون

ارتباط بین نخ‌ها با Queue

گاهی نیاز دارید نخ‌ها با هم داده ردوبدل کنند. در این حالت می‌توان از کلاس Queue استفاده کرد.

import threading

import queue

import time




def producer(q):

    for i in range(5):

        print(f"تولید داده: {i}")

        q.put(i)

        time.sleep(1)




def consumer(q):

    while True:

        item = q.get()

        print(f"مصرف داده: {item}")

        q.task_done()




q = queue.Queue()

t1 = threading.Thread(target=producer, args=(q,))

t2 = threading.Thread(target=consumer, args=(q,))




t1.start()

t2.start()




t1.join()

q.join()

در این مثال:

  • نخ تولیدکننده داده‌ها را وارد صف می‌کند.
  • نخ مصرف‌کننده داده‌ها را از صف می‌گیرد و پردازش می‌کند.

این روش برای ساخت برنامه‌های همزمان (مثل چت آنلاین یا سیستم‌های جمع‌آوری داده) بسیار پرکاربرد است.

اگر در حال یادگیری پروژه‌های کاربردی مثل تحلیل داده با پایتون هستید، ترکیب چندنخی با کار روی داده‌های بزرگ می‌تواند سرعت پردازش را به شکل چشمگیری افزایش دهد. چالش‌ها و راهکارهای عملی

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

مشکل GIL (Global Interpreter Lock)

بزرگ‌ترین محدودیت چندنخی در پایتون GIL است. این قفل باعث می‌شود در هر لحظه فقط یک نخ بتواند کد پایتون را اجرا کند؛ حتی اگر پردازنده شما چند هسته‌ای باشد.

پیامد:

  • نخ‌ها برای وظایف CPU-Bound (مثل محاسبات ریاضی سنگین یا پردازش تصویر) کارایی زیادی ندارند.
  • در عوض، برای وظایف I/O-Bound (مثل خواندن فایل، درخواست وب، یا اتصال به دیتابیس) عالی عمل می‌کنند.

راهکار:

  • اگر پروژه CPU-Bound دارید، به جای threading از multiprocessing استفاده کنید.
  • برای پروژه‌های I/O-Bound، threading همچنان بهترین انتخاب است.

مدیریت Race Condition

وقتی چند نخ همزمان روی یک متغیر یا منبع مشترک کار می‌کنند، نتایج غیرمنتظره ایجاد می‌شود.

 راهکار:

  • استفاده از Lock، RLock یا Semaphore برای کنترل دسترسی نخ‌ها.
  • استفاده از Queue به جای به‌اشتراک‌گذاری مستقیم منابع.

 

 مشکل Deadlock

وقتی دو نخ هر کدام منتظر آزاد شدن قفل دیگری بمانند، برنامه در یک بن‌بست گیر می‌کند.

 راهکار:

  • استفاده‌ی هوشمندانه از Lock و اجتناب از قفل‌های تو در تو.
  • به‌کارگیری timeout هنگام گرفتن قفل‌ها.

 

 سربار مدیریتی نخ‌ها

ایجاد تعداد زیادی نخ می‌تواند مصرف حافظه را افزایش داده و برنامه را کندتر کند.

 راهکار:

  • به جای ایجاد نخ‌های زیاد، از Thread Pooling استفاده کنید (با concurrent.futures).
  • برای وظایف سبک و زیاد، AsyncIO جایگزین مناسبی است.

 

 خطایابی سخت‌تر

دیباگ کردن برنامه‌های چندنخی به دلیل اجرای همزمان کدها دشوارتر است.

 راهکار:

  • استفاده از لاگ‌گیری (logging) برای ردیابی وضعیت نخ‌ها.
  • تست واحد (Unit Testing) برای بررسی بخش‌های کوچک برنامه.

 

بسیاری از این چالش‌ها در سایر زبان‌های برنامه‌نویسی مثل پایتون یا C# هم وجود دارند. اما در پایتون به خاطر وجود GIL نیاز به دقت بیشتری در انتخاب بین Thread و Process دارید.

 

تا اینجا دیدیم که نخ‌ها علاوه بر مزایا، مشکلاتی هم دارند. اما خوشبختانه با انتخاب درست ابزار و رعایت الگوهای صحیح می‌توان این چالش‌ها را کنترل کرد.

 Thread Poolingاستخر نخ‌ها

بهینه‌سازی عملکرد

برای اینکه چندنخی در پایتون بیشترین بازدهی را داشته باشد، باید بدانیم چه زمانی از آن استفاده کنیم و چه تکنیک‌هایی برای بهبود عملکرد وجود دارد. در ادامه به مهم‌ترین روش‌های بهینه‌سازی می‌پردازیم:

 

 Thread Poolingاستخر نخ‌ها

ایجاد و مدیریت تعداد زیادی نخ هزینه‌بر است. به جای ایجاد نخ‌های جداگانه برای هر وظیفه، می‌توان از Thread Pool استفاده کرد.

با ThreadPoolExecutor می‌توانید مجموعه‌ای از نخ‌های آماده داشته باشید و کارها را بین آن‌ها تقسیم کنید:

from concurrent.futures import ThreadPoolExecutor

import time




def task(n):

    time.sleep(1)

    return f"وظیفه {n} انجام شد"




with ThreadPoolExecutor(max_workers=3) as executor:

    results = executor.map(task, range(5))

    print(list(results))

مزایا:

  • کاهش سربار ایجاد نخ‌های جدید
  • مدیریت ساده‌تر وظایف
  • افزایش کارایی در وظایف I/O-Bound

 

 IO-Bound vs CPU-Bound Tasks

برای بهینه‌سازی باید نوع وظیفه را بشناسیم:

  • IO-Bound وابسته به ورودی/خروجی:
    شامل کارهایی مثل خواندن فایل، دانلود داده یا کار با دیتابیس.

    • بهترین انتخاب: Threading یا concurrent.futures
  • CPU-Bound وابسته به پردازنده:
    شامل کارهای سنگین پردازشی مثل محاسبات ریاضی یا پردازش تصویر (مثلاً در پروژه‌های آموزش ریاضیات هوش مصنوعی)

    • بهترین انتخاب: Multiprocessing

 

 مقایسه عملکرد با AsyncIO

گاهی به جای استفاده از نخ‌ها، می‌توان از AsyncIO استفاده کرد.

  • Threading: اجرای موازی با استفاده از چند نخ. مناسب برای زمانی که نیاز به همزمانی در چند وظیفه دارید.
  • AsyncIO: اجرای وظایف به صورت غیرهمزمان در یک نخ واحد. مناسب برای کارهایی که بیشتر وقتشان در انتظار I/O می‌گذرد (مثل درخواست وب).

برای مثال، در یک وب‌اسکرپر اگر بخواهید همزمان صدها درخواست HTTP ارسال کنید، AsyncIO عملکرد بهتری خواهد داشت. اما اگر کارهای شما ترکیبی از I/O و پردازش سبک است، Threading انتخاب مناسبی است.

 

نکات کاربردی برای بهینه‌سازی

  1. قبل از استفاده از نخ‌ها، بررسی کنید که آیا واقعاً به آن‌ها نیاز دارید یا خیر.
  2. اگر پروژه‌تان مربوط به پردازش داده است، ترکیب چندنخی با تکنیک‌های تحلیل داده با پایتون می‌تواند بسیار موثر باشد.
  3. برای وظایف کوچک و زیاد، از AsyncIO یا Thread Pool استفاده کنید.
  4. همیشه مصرف حافظه و زمان اجرای برنامه را اندازه‌گیری کنید تا ببینید انتخابتان بهینه بوده یا خیر.

 

انتخاب بین threading، multiprocessing و AsyncIO بستگی به نوع وظایف شما دارد. درک این تفاوت‌ها همان چیزی است که یک برنامه‌نویس حرفه‌ای پایتون را از یک تازه‌کار متمایز می‌کند.

جمع بندی

چندنخی در پایتون روشی مؤثر برای اجرای همزمان وظایف است، مخصوصاً در کارهای I/O-Bound مثل دانلود فایل یا کار با دیتابیس. اما به دلیل وجود GIL، نخ‌ها برای کارهای CPU-Bound مناسب نیستند و در این حالت باید از multiprocessing استفاده کرد. انتخاب درست بین Threading، Multiprocessing و AsyncIO کلید بهینه‌سازی عملکرد برنامه‌های پایتون است.

اگر سوالی دارید یا نیاز به راهنمایی در مسیر یادگیری پایتون دارید، تیم دیتایاد آماده است تا شما را همراهی کند. برای مشاوره رایگان، با شماره  ۰۹۹۰۵۵۰۱۹۹۸     تماس بگیرید و شروع کنید!

سوالات متداول

1-چه زمانی از threading به جای multiprocessing استفاده کنیم؟

از threading زمانی استفاده کنید که برنامه I/O-Bound باشد (مثل خواندن فایل، کار با شبکه) و نیاز به اجرای همزمان وظایف سبک با مصرف حافظه کم دارید. برای کارهای CPU-Bound (محاسبات سنگین) بهتر است از multiprocessing استفاده شود.

2-یا استفاده از چندنخی همیشه باعث افزایش سرعت می‌شود؟

خیر. اگر وظایف شما CPU-Bound باشند، استفاده از چندنخی حتی ممکن است سرعت را کاهش دهد. در این حالت، بهتر است از multiprocessing یا زبان‌های دیگر مثل پایتون یا C# استفاده کنید.

3-چگونه می‌توان از بروز Race Condition جلوگیری کرد؟

  • استفاده از Lock برای کنترل دسترسی نخ‌ها به منابع مشترک.
  • استفاده از Queue به جای به‌اشتراک‌گذاری مستقیم متغیرها.

4-آیا چندنخی در پایتون واقعاً موازی اجرا می‌شود؟

خیر، به دلیل وجود GIL (Global Interpreter Lock)، در هر لحظه فقط یک نخ می‌تواند کد پایتون را اجرا کند. اما برای کارهای I/O-Bound، چندنخی باعث می‌شود برنامه همزمان به نظر برسد و سرعت کلی افزایش پیدا کند.

نویسنده: رضا علیپور

این مطالب را هم مشاهده کنید

0 نظرات
قدیمی‌ترین
تازه‌ترین بیشترین رأی
بازخورد (Feedback) های اینلاین
مشاهده همه دیدگاه ها