ویژگی daemon
گاهی میخواهیم یک ترد در «پسزمینه» اجرا شود؛ یعنی کارش را انجام دهد اما مانع پایانیافتن برنامه نشود. برای این کار میتوان ترد را بهصورت daemon تعریف کرد. تردها بهصورت پیشفرض daemon نیستند.
تفاوت کلیدی در «پایان زندگی» است: برنامهی پایتون تا زمانی که حتی یک تردِ غیرdaemon در حال اجرا باشد، زنده میماند و منتظر آن میماند. اما تردهای daemon اینطور نیستند؛ بهمحض اینکه همهی تردهای غیرdaemon (از جمله ترد اصلی) تمام شوند، تردهای daemon بهصورت خودکار و بدون تکمیل کارشان بسته میشوند. بههمین دلیل daemon برای «سناریوهای فرعی» مناسب است.
سناریوی فرعی، کاری است که حذفشدنش خللی در هدف اصلی برنامه ایجاد نمیکند و برنامه همچنان خروجی درست میدهد. چند نمونه:
- مانیتورینگ منابع (مثل مصرف حافظه یا CPU) هنگام اجرا
- ارسال گزارش (log) هنگام اجرا
- ارسال اعلانها یا هشدارها هنگام اجرا
- پاکسازی حافظه یا کش هنگام اجرا
- ذخیرهسازی یا پشتیبانگیری خودکار هنگام اجرا
مثال: اطلاعرسانی به کاربر
برنامهای داریم که اجرایش زمان قابلتوجهی طول میکشد. در این مدت ممکن است کاربر تصور کند برنامه هنگ کرده و آن را ببندد. برای جلوگیری از این تصور، میخواهیم اگر کار بیش از سه ثانیه طول کشید، پیامی نمایش دهیم و از کاربر بخواهیم کمی بیشتر صبر کند. برای این کار تابع check_thread_status را نوشتهایم که یک ترد میگیرد و اگر بعد از سه ثانیه هنوز زنده بود، پیام میدهد.
import time
from threading import Thread
def task(name):
print(f'task {name} started')
time.sleep(1)
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()توضیح کد بالا:
- تابع
taskیک کار یکثانیهای را شبیهسازی میکند. - تابع
check_thread_statusیک ترد میگیرد و پس از سه ثانیه، اگر ترد هنوز زنده باشد، پیامی نمایش میدهد. - ترد
t1تابعtaskو تردt2تابعcheck_thread_statusرا اجرا میکند.
مشکل این کد این است که تسک اصلی در یک ثانیه تمام میشود، اما چون check_thread_status سه ثانیه sleep میکند و یک تردِ غیرdaemon است، برنامه منتظر آن میماند و کل اجرا سه ثانیه طول میکشد؛ در حالی که کار مفید خیلی زودتر تمام شده بود.
حالا با یک تغییر کوچک، check_thread_status را به یک ترد daemon تبدیل میکنیم تا در پسزمینه اجرا شود و مانع پایان برنامه نگردد.
import time
from threading import Thread
def task(name):
print(f'task {name} started')
time.sleep(1)
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, ), daemon=True)
t1.start()
t2.start()حالا وقتی task بعد از یک ثانیه تمام میشود، تنها تردِ غیرdaemon (ترد اصلی) هم به پایان میرسد و برنامه بلافاصله بسته میشود؛ ترد daemon نیمهکاره رها میشود و دیگر سه ثانیه معطل نمیمانیم. دقیقاً همان رفتاری که برای یک کار فرعی میخواهیم: اگر کار اصلی تمام شد، منتظر کار فرعی نمیمانیم.
روشهای تعریف یک ترد daemon
دو روش برای daemon کردن یک ترد وجود دارد.
۱. هنگام تعریف ترد
t2 = Thread(target=check_thread_status, args=(t1, ), daemon=True)۲. پس از تعریف ترد (پیش از start)
t2 = Thread(target=check_thread_status, args=(t1, ))
t2.daemon = Trueخواندن و تنظیم daemon
در نسخههای قدیمی پایتون دو متد برای این کار وجود داشت: isDaemon() که مقدار ویژگی daemon را برمیگرداند، و setDaemon() که یک مقدار boolean میگیرد و آن را تنظیم میکند. اما این متدها منسوخ (deprecated) شدهاند و توصیه میشود مستقیماً با ویژگی daemon کار کنید (خواندن با t.daemon و تنظیم با t.daemon = True).
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=('Firooze', ))
t3 = Thread(target=say_hi, args=('Hamid', ), daemon=True)
t4 = Thread(target=say_hi, args=('Saeed', ))
t4.daemon = True # preferred
t4.setDaemon(True) # deprecated
t1.start()
t2.start()
t3.start()
t4.start()
print('t1', t1.daemon) # preferred
print('t1', t1.isDaemon()) # deprecated
print('t2', t2.daemon) # preferred
print('t2', t2.isDaemon()) # deprecated
print('t3', t3.daemon) # preferred
print('t3', t3.isDaemon()) # deprecated
print('t4', t4.daemon) # preferred
print('t4', t4.isDaemon()) # deprecatedنکتهی مهم: زمان تنظیم daemon
ویژگی daemon را باید پیش از فراخوانی start تنظیم کنید. اگر بعد از استارتشدن ترد بخواهید آن را تغییر دهید، با خطای RuntimeError روبهرو میشوید. کد زیر خطا میدهد:
import time
from threading import Thread
def say_hi(name):
time.sleep(1)
print('hi', name)
t1 = Thread(target=say_hi, args=('mmreza', ))
t1.start()
t1.daemon = True # RuntimeError: cannot set daemon status of active threadنکتهی تکمیلی: daemon و join
یک نکتهی کاربردی که خوب است بدانید: چون تردهای daemon ممکن است هر لحظه و بهصورت ناگهانی (هنگام پایان برنامه) بسته شوند، برای کارهایی که حتماً باید تا انتها اجرا شوند — مثل نوشتن کامل یک فایل یا بستن درست یک اتصال — از daemon استفاده نکنید، چون ممکن است وسط کار قطع شوند. daemon فقط برای کارهای فرعیِ قابلصرفنظر مناسب است. اگر لازم است منتظر پایان یک ترد بمانید، از join() استفاده کنید (که برای تردهای daemon هم کار میکند و تا پایان آن ترد صبر میکند).
خلاصه
| ویژگی | daemon = False | daemon = True |
|---|---|---|
| حالت پیشفرض | بله | خیر |
| اجرا در پسزمینه | خیر | بله |
| پایان زندگی | برنامهی اصلی تا پایان اجرای آن صبر میکند. | اگر همهی تردهای غیرdaemon تمام شوند، بهصورت خودکار بسته میشود. |
- ترد daemon برای کارهای فرعیِ پسزمینه است که پایاننیافتنشان مشکلی ایجاد نمیکند.
- daemon را با
daemon=Trueهنگام ساخت یا باt.daemon = Trueپیش ازstartتنظیم کنید. - متدهای
isDaemon()وsetDaemon()منسوخاند؛ مستقیماً با ویژگیdaemonکار کنید. - تنظیم
daemonپس ازstartخطایRuntimeErrorمیدهد.