آموزش پایتون - استخرهای Thread

استخر نخ (Thread pool) مکانیزمی است که به‌طور خودکار چندین نخ را به‌صورت کارآمد مدیریت می‌کند و امکان اجرای هم‌زمان وظایف را فراهم می‌سازد. پایتون قابلیت استخر نخ را مستقیماً از طریق ماژول threading ارائه نمی‌دهد.

در عوض، پایتون استخر مبتنی بر نخ را از طریق ماژول multiprocessing.dummy و ماژول concurrent.futures ارائه می‌دهد. این ماژول‌ها رابط‌های مناسبی برای ایجاد و مدیریت استخرهای نخ فراهم می‌کنند و اجرای هم‌زمان وظایف را آسان‌تر می‌سازند.

استخر نخ (Thread Pool) چیست؟

استخر نخ مجموعه‌ای از نخ‌ها است که توسط یک استخر مدیریت می‌شوند. به هر نخ در این استخر، یک کارگر (worker) یا نخ کارگر (worker thread) گفته می‌شود. این نخ‌ها می‌توانند برای انجام چندین وظیفه دوباره مورد استفاده قرار گیرند، که این امر بار ناشی از ایجاد و نابودی مکرر نخ‌ها را کاهش می‌دهد.

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

ما می‌توانیم استخرهای نخ را در پایتون با استفاده از کلاس‌های زیر پیاده‌سازی کنیم −

  • کلاس ThreadPool در پایتون
  • کلاس ThreadPoolExecutor در پایتون

استفاده از کلاس ThreadPool در پایتون

کلاس multiprocessing.pool.ThreadPool یک رابط استخر نخ را در داخل ماژول multiprocessing ارائه می‌دهد. این کلاس استخری از نخ‌های کارگر را مدیریت می‌کند که می‌توان کارهایی را برای اجرای هم‌زمان به آن‌ها ارسال کرد.

یک شیء ThreadPool مدیریت چندین نخ را با دست و پنجه نرم کردن با ایجاد و توزیع وظایف بین نخ‌های کارگر ساده می‌کند. این کلاس رابط مشترکی با کلاس Pool دارد که در اصل برای فرآیندها (processes) طراحی شده بود، اما به‌گونه‌ای تنظیم شده است که با نخ‌ها نیز کار کند.

نمونه‌های ThreadPool از نظر رابط به‌طور کامل با نمونه‌های Pool سازگار هستند و باید یا به عنوان یک مدیر بافت (context manager) یا با فراخوانی دستی close() و terminate() مدیریت شوند.

مثال

این مثال اجرای موازی توابع square (مربع) و cube (مکعب) را روی لیستی از اعداد با استفاده از استخر نخ پایتون نشان می‌دهد، جایی که هر تابع به‌صورت هم‌زمان با حداکثر ۳ نخ و با تاخیر ۱ ثانیه‌ای بین اجراها روی اعداد اعمال می‌شود.

from multiprocessing.dummy import Pool as ThreadPool
import time

def square(number):
   sqr = number * number
   time.sleep(1)
   print("Number:{} Square:{}".format(number, sqr))

def cube(number):
   cub = number*number*number
   time.sleep(1)
   print("Number:{} Cube:{}".format(number, cub))

numbers = [1, 2, 3, 4, 5]
pool = ThreadPool(3)
pool.map(square, numbers)
pool.map(cube, numbers)

pool.close()

خروجی

با اجرای کد بالا خروجی زیر را دریافت خواهید کرد −

Number:2 Square:4
Number:1 Square:1
Number:3 Square:9
Number:4 Square:16
Number:5 Square:25
Number:1 Cube:1
Number:2 Cube:8
Number:3 Cube:27
Number:4 Cube:64
Number:5 Cube:125

استفاده از کلاس ThreadPoolExecutor در پایتون

کلاس ThreadPoolExecutor از ماژول concurrent.futures پایتون، یک رابط سطح بالا برای اجرای ناهم‌گام (asynchronous) توابع با استفاده از نخ‌ها ارائه می‌دهد. ماژول concurrent.futures شامل کلاس Future و دو کلاس Executor است − ThreadPoolExecutor و ProcessPoolExecutor.

کلاس Future

کلاس concurrent.futures.Future مسئول مدیریت اجرای ناهم‌گام هر شیء قابل فراخوانی (callable) مانند یک تابع است. برای به دست آوردن یک شیء Future، باید متد submit() را روی هر شیء Executor فراخوانی کنید. این شیء نباید مستقیماً توسط سازنده (constructor) آن ایجاد شود.

متدهای مهم در کلاس Future عبارتند از −

  • result(timeout=None): این متد مقداری را که توسط فراخوانی برگردانده شده است، بازمی‌گرداند. اگر فراخوانی هنوز کامل نشده باشد، این متد تا timeout ثانیه منتظر می‌ماند. اگر فراخوانی در زمان timeout ثانیه کامل نشود، یک TimeoutError رخ خواهد داد. اگر timeout مشخص نشده باشد، هیچ محدودیتی برای زمان انتظار وجود ندارد.
  • cancel(): این متد تلاش می‌کند فراخوانی را لغو کند. اگر فراخوانی در حال حاضر در حال اجرا باشد یا اجرای آن تمام شده باشد و قابل لغو نباشد، این متد مقدار بولین False را بازمی‌گرداند. در غیر این صورت فراخوانی لغو شده و متد مقدار True را بازمی‌گرداند.
  • cancelled(): اگر فراخوانی با موفقیت لغو شده باشد، مقدار True را بازمی‌گرداند.
  • running(): اگر فراخوانی در حال حاضر در حال اجرا باشد و قابل لغو نباشد، مقدار True را بازمی‌گرداند.
  • done(): اگر فراخوانی با موفقیت لغو شده باشد یا اجرای آن به پایان رسیده باشد، مقدار True را بازمی‌گرداند.

کلاس ThreadPoolExecutor

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

concurrent.futures.ThreadPoolExecutor(max_threads)

مثال

در اینجا مثالی آورده شده است که از کلاس concurrent.futures.ThreadPoolExecutor برای مدیریت و اجرای ناهم‌گام وظایف در پایتون استفاده می‌کند. به طور خاص، نحوه ارسال چندین وظیفه به یک استخر نخ و نحوه بررسی وضعیت اجرای آن‌ها را نشان می‌دهد.

from concurrent.futures import ThreadPoolExecutor
from time import sleep
def square(numbers):
   for val in numbers:
      ret = val*val
      sleep(1)
      print("Number:{} Square:{}".format(val, ret))
def cube(numbers):
   for val in numbers:
      ret = val*val*val
      sleep(1)
      print("Number:{} Cube:{}".format(val, ret))
if __name__ == '__main__':
   numbers = [1,2,3,4,5]
   executor = ThreadPoolExecutor(4)
   thread1 = executor.submit(square, (numbers))
   thread2 = executor.submit(cube, (numbers))
   print("Thread 1 executed ? :",thread1.done())
   print("Thread 2 executed ? :",thread2.done())
   sleep(2)
   print("Thread 1 executed ? :",thread1.done())
   print("Thread 2 executed ? :",thread2.done())

این کد خروجی زیر را تولید خواهد کرد −

Thread 1 executed ? : False
Thread 2 executed ? : False
Number:1 Square:1
Number:1 Cube:1
Thread 1 executed ? : False
Thread 2 executed ? : False
Number:2 Square:4
Number:2 Cube:8
Number:3 Square:9
Number:3 Cube:27
Number:4 Square:16
Number:4 Cube:64
Number:5 Square:25
Number:5 Cube:125

پیشرفت مطالعه: وضعیت خوانده‌شدن این درس فقط در همین مرورگر ذخیره می‌شود؛ بدون نیاز به حساب کاربری.