آموزش ماژول threading – کلاس Thread

Please login to bookmark Close

مقدمه

برای مولتی تردینگ می‌توان از کلاس 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 از آن یا ارث بری از آن امکان پذیر است.
نامتوضیحاتMethodAttributeوضعیت
startشروع اجرای نخ
runمتدی که باید در نخ اجرا شود
joinمنتظر می‌ماند تا نخ به اتمام برسد
is_aliveبررسی می‌کند که آیا نخ هنوز در حال اجرا است یا خیر
getNameدریافت نام نخمنسوخ
setNameتنظیم نام نخمنسوخ
nameدریافت و تنظیم نام نخ
daemonتنظیم یا دریافت وضعیت دیمونی نخ
identشناسه (ID) یکتای نخ
isDaemonبررسی می‌کند که آیا نخ یک دیمون است یا خیرمنسوخ
setDaemonمقدار daemon را برای یک نخ تنظیم می‌کندمنسوخ
لیست method ها و attribute های کلاس Thread
نام تابعتوضیحات
current_threadآبجکت thread فعلی را بر‌میگرداند.
active_countتعداد thread های فعال را برمی‌گرداند.
enumerateلیستی از همه thread های فعال را در می‌گرداند.
توابع کاربردی ماژول threading
Please login to bookmark Close
پیشرفت شما در «دوره آموزش کانکارنسی در پایتون» (19%)
نظرات

دیدگاهتان را بنویسید

19%
پیشرفت
فهرست مطالب

تمرین

این قسمت تمرین ندارد!

پاسخ تمرین ها

هنوز برای تمرین‌های این قسمت پاسخی ثبت نشده است!

اشتراک گذاری

چرا بهتره از فیلترشکن استفاده کنید؟

من همه ویدئو ها و پادکست های کُدباز رو توی یوتیوب و ساندکلود و پلتفرم هایی آپلود می‌کنم که اغلب فیلتر هستند.

اغلب آموزش‌ها ویدئو و پادکست دارند. پس اگر می‌خواهید از محتوای سایت بیشترین استفاده رو ببرید نیاز به فیلتر شکن دارید.

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

تنظیمات

انتخاب زبان
تغییر تم