مقدمه
برای مولتی تردینگ میتوان از کلاس Thread استفاده کرد.. ساخت ترد با این کلاس به دو روش امکان پذیر است.
در ادامه هر یک از این روش ها و متد ها و توابع کاربردی این کلاس را بررسی خواهیم کرد.
ساخت ترد با ایجاد instance از کلاس Thread
به کد زیر توجه کنید. این کد حاوی یک تابع است که اجرای آن زمانبر است. زمانبر بودن تابع با استفاده از time.sleep شبیهسازی شده است.
import time
def say_hi(name):
time.sleep(1)
print('hi', name)
say_hi('Ali')
say_hi('Hamid')
say_hi('Reza')
say_hi('Sahar')کافیاست تغییرات زیر را در کد بالا ایجاد کنیم تا با ایجاد چند ترد، سرعت اجرا افزایش یابد.
import time
from threading import Thread
def say_hi(name):
time.sleep(1)
print('hi', name)
t1 = Thread(target=say_hi, args=('Ali', ))
t2 = Thread(target=say_hi, args=('Hamid', ))
t3 = Thread(target=say_hi, args=('Reza', ))
t4 = Thread(target=say_hi, args=('Sahar', ))
t1.start()
t2.start()
t3.start()
t4.start()توضیح کد بالا:
- خط ۲: کلاس
Threadرا import کردهایم. - خط ۸ تا ۱۱: در این قسمت ترد ها را ساختهایم.
- خط ۱۳ تا ۱۶: در این قسمت ترد هایی که ساختهایم را اجرا کردهایم.
هر ترد باید بداند که چه تابعی را با چه ورودی هایی باید اجرا کند. بدین منظور، آن تابع را به عنوان target و ورودی هایش را به عنوان args به کلاس Thread ارسال میکنیم.
در واقع تکه کد زیر یک ترد میسازد که در آن تابع say_hi با ورودی Ali را اجرا میکند.
Thread(target=say_hi, args=('Ali', ))ساخت ترد با ارث بری از کلاس Thread
اکنون کد زیر را در نظر بگیرید.
import time
def say_hi(name):
time.sleep(1)
print('hi', name)
say_hi('mmreza')
say_hi('Fatima')
say_hi('Mahboobeh')
say_hi('Saeed')این بار، مولتی تردینگ را با ارث بری از کلاس Thread و انجام دو مرحله زیر پیاده سازی میکنیم.
- extend کردن متد
__init__با تابعsuper - override کردن متد
run
import time
from threading import Thread
class MyThread(Thread):
def __init__(self, target, args):
super().__init__()
self.target = target
self.args = args
def run(self):
self.target(*self.args)
def say_hi(name):
time.sleep(1)
print('hi', name)
t1 = MyThread(target=say_hi, args=('mmreza', ))
t2 = MyThread(target=say_hi, args=('Fatima', ))
t3 = MyThread(target=say_hi, args=('Mahboobeh', ))
t4 = MyThread(target=say_hi, args=('Saeed', ))
t1.start()
t2.start()
t3.start()
t4.start()توضیح کد بالا:
- خط ۲: در این خط کلاس
Threadرا import کردهایم. - خط ۴ تا ۱۱: کلاس
MyThreadرا با ارثبری از کلاسThreadساختهایم.- خط ۵ تا ۸: متد
__init__را به شکلی پیادهسازی کردهایم که بیشترین شباهت را به متد__init__کلاسThreadرا داشته باشد. یعنی آبجکت تابع را به عنوانtargetو همه ورودیهای مورد نیازش را به عنوانargsگرفتهایم. - خط ۱۰ تا ۱۱: در این بخش متد
runرا override کردهایم به نحوی که همه ورودیهای target را با عملگر*از طریقargsبه آن ارسال کرده ایم.
- خط ۵ تا ۸: متد
- خط ۱۷ تا ۲۰: ترد ها را ساختهایم.
- خط ۲۲ تا ۲۵: ترد ها را اجرا کردهایم.
مقایسه روشهای ایجاد ترد با کلاس Thread
روش دوم پیچیده تر است زیرا نیاز به دانش شی گرایی دارد اما، امکانات بیشتری از نظر معماری نرمافزار در اختیار ما میگذارد و آزادی عمل بیشتری در معماری نرمافزار داریم. پس میتوانیم امکانات بیشتری داشته باشیم. مثلا میتوانیم انواع Design Pattern ها را با آن پیادهسازی و استفاده کنیم.
| ویژگی | روش اول – ساخت instance | روش دوم – ارث بری |
| سادگی استفاده | بیشتر | کمتر |
| انعطاف پذیری | کمتر | بیشتر |
| تعداد خطوط کد | کمتر | بیشتر |
| خوانایی کد | خواناتر برای کارهای ساده | خواناتر برای کارهای پیچیده تر |
| مدیریت حالت (state) | سختتر برای حالات پیچیده | راحتتر |
| قابلیت استفاده مجدد | کمتر | بیشتر (در صورت درست بودن معماری) |
متد ها و توابع کلاس Thread
start
پس از اجرای این متد، سیستمعامل در اولین فرصتی که امکان پذیر باشد ترد را اجرا میکند.
run
این متد وظیفه اجرای کدی را دارد که به عنوان target هنگام ساخت ترد به آن ارسال شده است.
join
هر جا که این متد فراخوانی شود، main thread در آن نقطه صبر میکند تا اجرای آن ترد به پایان برسد.
به کد زیر توجه کنید.
import time
from threading import Thread
def task(name):
print(f'task {name} started')
time.sleep(1)
print(f'task {name} finished')
t1 = Thread(target=task, args=(1, ))
t2 = Thread(target=task, args=(2, ))
t1.start()
t2.start()
print('End of the program!')در این کد، عبارت End of the program قبل از اینکه t1 و t2 به پایان برسند چاپ میشود. برای اینکه این عبارت پس از اجرای کامل هر دو ترد چاپ شود باید این تکه کد را با متد join به شکل زیر اصلاح کنیم.
import time
from threading import Thread
def task(name):
print(f'task {name} started')
time.sleep(1)
print(f'task {name} finished')
t1 = Thread(target=task, args=(1, ))
t2 = Thread(target=task, args=(2, ))
t1.start()
t2.start()
t1.join()
t2.join()
print('End of the program!')اکنون در مثالی دیگر به کد زیر توجه کنید.
import time
from threading import Thread
def task(name):
print(f'task {name} started')
time.sleep(1)
print(f'task {name} finished')
t1 = Thread(target=task, args=(1, ))
t2 = Thread(target=task, args=(2, ))
t1.start()
t2.start()مساله ای که در مورد کد بالا وجود دارد این است که نمیتوان تضمین کرد که اجرای کدام ترد زودتر به پایان میرسد. اگر کد بالا را چندین بار اجرا کنید متوجه خواهید شد که گاهی t1 زودتر به پایان میرسد و گاهی هم t2 و این کاملا تصادفی است زیرا به نحوه مدیریت تسک ها توسط سیستم عامل وابسته است.
اگر بخواهیم تضمین کنیم که t2 پس از اتمام t1 اجرا شود، باید از تابع join استفاده کنیم.
import time
from threading import Thread
def task(name):
print(f'task {name} started')
time.sleep(1)
print(f'task {name} finished')
t1 = Thread(target=task, args=(1, ))
t2 = Thread(target=task, args=(2, ))
t1.start()
t1.join()
t2.start()همانطور که مشاهده میکنید، این بار t2 شروع نمیشود مگر اینکه t1 به اتمام رسیده باشد.
پس متد join برای موارد زیر به کار میرود:
- ایجاد ایستگاه هایی برای اطمینان از اجرای کامل یک ترد، قبل از اجرای ادامه برنامه
- ایجاد تقدم و تاخر در اجرای ترد ها
is_alive
با استفاده از این متد میتوان فهمید که یک thread در حال اجراست یا خیر.
فرض کنید اجرای یک ترد ممکن است تحت شرایط خاصی زمان بر تر از چیزی باشد که کاربر تصور میکند. برای اینکه به کاربر اطلاع دهیم که برنامه در حال اجراست و لازم است کمی صبور باشد میتوان این متد را به شکلی که در کد زیر استفاده شده به کار گرفت.
import time
from threading import Thread
def task(name):
print(f'task {name} started')
time.sleep(5)
print(f'task {name} finished')
def check_thread_status(thread):
time.sleep(3)
if thread.is_alive():
print(f"{thread.name} is still running! please wait ...")
t1 = Thread(target=task, args=(1, ))
t2 = Thread(target=check_thread_status, args=(t1, ))
t1.start()
t2.start()توضیح کد بالا:
- خط ۴ تا ۷: یک تابع تعریف شده که اجرایش ۵ ثانیه زمان میبرد.
- خط ۹ تا ۱۲: یک تابع تعریف شده که یک ترد میگیرد و پس از ۳ ثانیه اگر هنوز در حال اجرا باشد با چاپ کردن یک پیام، وضعیت اجرای برنامه را به کاربر اطلاع میدهد.
- خط ۱۴: یک thread به نام t1 ساخته میشود که قرار است تابع
taskرا اجرا کند. - خط ۱۵: یک thread به نام t2 ساخته میشود که قرار است تابع
check_thread_statusرا اجرا کند.
getName
نام یک thread را برمیگرداند. این متد از پایتون نسخه 3.10 منسوخ شده است. به جای استفاده از آن میتوانید از ویژگی name استفاده کنید. لطفا به کد زیر توجه کنید:
import time
from threading import Thread
def task():
time.sleep(1)
t1 = Thread(target=task)
t1.start()
# BAD: DON'T USE THIS
print(t1.getName())
# GOOD: USE THIS
print(t1.name) خروجی کد بالا مشابه زیر خواهد بود:
DeprecationWarning: getName() is deprecated, get the name attribute instead
print(t1.getName())
Thread-1 (task)
Thread-1 (task)
در این خروجی یک هشدار نمایش داده میشود که به شما اطلاع میدهد که این متد منسوخ شده است.
setName
با استفاده از آن میتوان یک نام جدید به یک ترد داد. این متد از پایتون نسخه 3.10 منسوخ شده است. به جای استفاده از آن میتوانید از ویژگی name استفاده کنید. لطفا به کد زیر توجه کنید:
import time
from threading import Thread
def task():
time.sleep(1)
t1 = Thread(target=task)
t1.start()
print('old name is', t1.name)
t1.setName('Greate Thread 1')
print('new name is', t1.name)توضیح کد بالا:
در این کد با استفاده از متد setName نام thread را به Greate Thread 1 تغییر دادیم. خروجی این کد مشابه زیر خواهد بود.
old name is Thread-1 (task)
DeprecationWarning: setName() is deprecated, set the name attribute instead
t1.setName('Greate Thread 1')
new name is Greate Thread 1
همانطور که مشاهده میکنید نام thread تغییر کرده اما یک هشدار نیز چاپ شده است که به شما اطلاع میدهد که setName منسوخ شده و دیگر از آن استفاده نکنید.
name
با استفاده از این ویژگی میتوان نام یک ترد را بگیریم یا آن را تغییر دهیم. این ویژگی جایگزین متدهای getName و setName است. لطفا به کد زیر توجه کنید.
import time
from threading import Thread
def task():
time.sleep(1)
t1 = Thread(target=task)
t1.start()
print('old name is', t1.name)
t1.name = 'Greate Thread 1'
print('new name is', t1.name)daemon
با استفاده از این ویژگی میتوان مشخص کرد که یک ترد در پسزمینه اجرا شود یا نه. این ویژگی در این بخش به تفصیل آموزش داده شده است. به همین دلیل در این قسمت از توضیح آن صرف نظر میکنم.
ident
سیستم عامل به هر ترد که در حال اجراست یا اجرایش تمام شده یک عدد صحیح یکتا و منحصر به فرد به نام ident اختصاص میدهد. این عدد ممکن است در سیستم عامل های مختلف فرمت های متفاوتی داشته باشد.
برای دسترسی به این عدد کافیست از ویژگی ident استفاده کنید.
import time
from threading import Thread
def task(name):
print(f'task {name} started')
time.sleep(1)
print(f'task {name} finished')
t1 = Thread(target=task, args=(1, ))
t2 = Thread(target=task, args=(2, ))
t3 = Thread(target=task, args=(3, ))
t1.start()
t1.join()
t2.start()
print(f't1 ident is {t1.ident}')
print(f't2 ident is {t2.ident}')
print(f't3 ident is {t3.ident}')
t3.start()توضیح کد بالا:
- خط ۴ تا ۷: یک تابع تعریف کردهایم که اجرایش یک ثانیه زمان میبرد.
- خط ۹ تا ۱۱: سه thread ساختهایم.
- خط ۱۳ تا ۱۹: سه thread ساخته ایم و مقدار ident مربوط به هر یک را در حالات مختلف نمایش دادهایم.
- خط ۱۶: مقدار ident را برای t1 نمایش دادهایم که حاوی یک عدد است. زمانی که این خط اجرا میشود، اجرای t1 پایان یافته است.
- خط ۱۷: مقدار ident را برای t2 نمایش دادهایم که حاوی یک عدد است. زمانی که این خط اجرا میشود، t2 در حال اجراست.
- خط ۱۸: مقدار ident را برای t3 نمایش دادهایم که حاوی None است. زمانی که این خط اجرا میشود، اجرای t3 هنوز شروع نشده است.
خروجی کد بالا مشابه زیر است.
task 1 started
task 1 finished
task 2 started
t1 ident is 128540213249728
t2 ident is 128540213249728
t3 ident is None
task 3 started
task 2 finished
task 3 finished
با توجه به این خروجی میتوان نتیجه گرفت که اگر یک thread هنوز start نشده باشد مقدار ident آن برابر None است.
این عدد برای موارد زیر به کار گرفته میشود.
- تشخیص یک ترد از ترد های دیگر
- لاگ گرفتن و دیباگ کردن راحت تر ترد ها
isDaemon
این متد در این بخش به تفصیل توضیح داده شده است.
setDaemon
این متد در این بخش به تفصیل توضیح داده شده است.
توابع کاربردی
current_thread
آبجکت تردی که در حال اجراست را برمیگرداند.
import time
from threading import Thread, current_thread
def task():
print(f'task {current_thread().name} started')
time.sleep(1)
print(f'task {current_thread().name} finished')
t1 = Thread(target=task).start()active_count
تعداد thread هایی که در در حال اجرا هستند را برمیگرداند. توجه داشته باشید که main thread نیز خودش یک ترد است و در شمارش تعداد ترد ها لحاظ میشود.
import time
from threading import Thread, active_count
def task():
time.sleep(1)
t1 = Thread(target=task)
t2 = Thread(target=task)
t1.start()
print(active_count())
t2.start()
print(active_count())توضیح کد بالا:
- خط ۱۱: این تابع در این خط تعداد ترد های در حال اجرا را ۲ برمیگرداند. در اینجا t1 و main thread، ترد هایی هستند که در حال اجرا هستند.
- خط ۱۳: این تابع در این خط تعداد ترد های در حال اجرا را ۳ برمیگرداند. در اینجا t1 و t2 و main thread، ترد هایی هستند که در حال اجرا هستند.
enumerate
آبجکت ترد هایی که در در حال اجرا هستند را برمیگرداند. توجه داشته باشید که main thread نیز یکی از ترد هایی است که این تابع برمیگرداند.
import time
from threading import Thread, enumerate
def task():
time.sleep(1)
t1 = Thread(target=task)
t2 = Thread(target=task)
t1.start()
print(enumerate())
t2.start()
print(enumerate())توضیح کد بالا:
- خط ۱۱: این تابع در این خط آبجکت ترد های در حال اجرا را برمیگرداند. در اینجا t1 و main thread، نخهایی هستند که در حال اجرا هستند.
- خط ۱۳: این تابع در این خط آبجکت ترد های در حال اجرا را برمیگرداند. در اینجا t1 و t2 و main thread، نخهایی هستند که در حال اجرا هستند.
خلاصه
- کلاس
Threadبرای مولتی تردینگ استفاده میشود. - مولتی تردینگ با کلاس
Threadبه روش ساختن instance از آن یا ارث بری از آن امکان پذیر است.
| نام | توضیحات | Method | Attribute | وضعیت |
start | شروع اجرای نخ | ✅ | ||
run | متدی که باید در نخ اجرا شود | ✅ | ||
join | منتظر میماند تا نخ به اتمام برسد | ✅ | ||
is_alive | بررسی میکند که آیا نخ هنوز در حال اجرا است یا خیر | ✅ | ||
getName | دریافت نام نخ | ✅ | منسوخ | |
setName | تنظیم نام نخ | ✅ | منسوخ | |
name | دریافت و تنظیم نام نخ | ✅ | ||
daemon | تنظیم یا دریافت وضعیت دیمونی نخ | ✅ | ||
ident | شناسه (ID) یکتای نخ | ✅ | ||
isDaemon | بررسی میکند که آیا نخ یک دیمون است یا خیر | ✅ | منسوخ | |
setDaemon | مقدار daemon را برای یک نخ تنظیم میکند | ✅ | منسوخ |
| نام تابع | توضیحات |
current_thread | آبجکت thread فعلی را برمیگرداند. |
active_count | تعداد thread های فعال را برمیگرداند. |
enumerate | لیستی از همه thread های فعال را در میگرداند. |