設定一個場景,在用戶了添加多個任務,點擊run task按鈕在后台處理這些tasks,並判斷task成功或失敗,因為task是耗時的,所以采用多線程方式處理tasks
考慮:
線程啟動后如何獲取task執行結果?
看代碼:
import threading import time class TaskThread(threading.Thread): """ 處理task相關的線程類 """ def __init__(self, func, args=()): super(TaskThread, self).__init__() self.func = func # 要執行的task類型 self.args = args # 要傳入的參數 def run(self): # 線程類實例調用start()方法將執行run()方法,這里定義具體要做的異步任務 print("start func {}".format(self.func.__name__)) # 打印task名字 用方法名.__name__ self.result = self.func(*self.args) # 將任務執行結果賦值給self.result變量 def get_result(self): # 改方法返回task函數的執行結果,方法名不是非要get_result try: return self.result except Exception as ex: print(ex) return "ERROR" def task_type1(task_id, task_name): print("start tasks, name:{}, id:{}".format(task_name, task_id)) time.sleep(2) print("end tasks, name:{}, id:{}".format(task_name, task_id)) return task_id thread_pool = [] # 列表用來保存線程實例 for i in range(10): # 循環創建線程對象 thread = TaskThread(task_type1, args=(i + 1, 'pay')) # 將線程對象添加到pool thread_pool.append(thread) # 起動線程 執行tasks thread.start() for thread in thread_pool: # 重要的一步,為什么一定要join thread.join() # 從線程pool中獲取結果 print("result:{}".format(thread.get_result()))
運行結果:
start func task_type1 start tasks, name:pay, id:1 start func task_type1 start tasks, name:pay, id:2 start func task_type1 start tasks, name:pay, id:3 start func task_type1 start tasks, name:pay, id:4 start func task_type1 start tasks, name:pay, id:5 start func task_type1 start tasks, name:pay, id:6 start func task_type1 start tasks, name:pay, id:7 start func task_type1 start tasks, name:pay, id:8 start func task_type1 start tasks, name:pay, id:9 start func task_type1 start tasks, name:pay, id:10 end tasks, name:pay, id:4 end tasks, name:pay, id:2 end tasks, name:pay, id:1 end tasks, name:pay, id:5 end tasks, name:pay, id:8 end tasks, name:pay, id:3 result:1 result:2 end tasks, name:pay, id:9 result:3 result:4 result:5 end tasks, name:pay, id:10 end tasks, name:pay, id:6 result:6 end tasks, name:pay, id:7 result:7 result:8 result:9 result:10
上面代碼實現了創建線程執行task,並獲取任務,關鍵點在於線程類中實現了ge_result方法,用來獲取任務函數的返回值,並且用到了thread.join(),這是必須的,如果沒有thread.join()將會怎么樣呢?
注釋掉:thread.join() 並重新運行代碼:
start func task_type1 start tasks, name:pay, id:1 start func task_type1 start tasks, name:pay, id:2 start func task_type1 start tasks, name:pay, id:3 start func task_type1 start tasks, name:pay, id:4 start func task_type1 start tasks, name:pay, id:5 start func task_type1 start tasks, name:pay, id:6 start func task_type1 start tasks, name:pay, id:7 start func task_type1 start tasks, name:pay, id:8 start func task_type1 start tasks, name:pay, id:9 start func task_type1 start tasks, name:pay, id:10 'TaskThread' object has no attribute 'result' result:ERROR 'TaskThread' object has no attribute 'result' result:ERROR 'TaskThread' object has no attribute 'result' result:ERROR 'TaskThread' object has no attribute 'result' result:ERROR 'TaskThread' object has no attribute 'result' result:ERROR 'TaskThread' object has no attribute 'result' result:ERROR 'TaskThread' object has no attribute 'result' result:ERROR 'TaskThread' object has no attribute 'result' result:ERROR 'TaskThread' object has no attribute 'result' result:ERROR 'TaskThread' object has no attribute 'result' result:ERROR end tasks, name:pay, id:1 end tasks, name:pay, id:3 end tasks, name:pay, id:2 end tasks, name:pay, id:5 end tasks, name:pay, id:6 end tasks, name:pay, id:7 end tasks, name:pay, id:4 end tasks, name:pay, id:8 end tasks, name:pay, id:9 end tasks, name:pay, id:10
如果沒有join,我們得到了這樣的結果,這是為什么呢?
這是因為self.result在run方法中,只有self.func執行結束后,self.resultb才被賦值,我們在調用get_result時,run方法並未執行結束,self.result自然也未被賦值,所以拋了'TaskThread' object has no attribute 'result'異常.
那為什么join以后就可以正常獲取self.result呢?
這就要理解一下多線程的原理:
1.當一個進程啟動之后,會默認產生一個主線程,因為線程是程序執行流的最小單元,當設置多線程時,主線程會創建多個子線程,在python中,默認情況下(其實就是setDaemon(False)),主線程執行完自己的任務以后,就退出了,此時子線程會繼續執行自己的任務
2. 如果setDaemon(True)方法,設置子線程為守護線程時,主線程一旦執行結束,則全部線程全部被終止執行,可能出現的情況就是,子線程的任務還沒有完全執行結束,就被迫停止,當然,這不是我們想要看到的
3. 我們希望看到的結果是當主線程結束后阻塞,等待子線程執行完成,這時需要用到join()
那能將join()放在start()后面嗎?
試一下:
thread.start()
thread.join()
執行結果:
start func task_type1 start tasks, name:pay, id:1 end tasks, name:pay, id:1 start func task_type1 start tasks, name:pay, id:2 end tasks, name:pay, id:2 start func task_type1 start tasks, name:pay, id:3 end tasks, name:pay, id:3 start func task_type1 start tasks, name:pay, id:4 end tasks, name:pay, id:4 start func task_type1 start tasks, name:pay, id:5 end tasks, name:pay, id:5 start func task_type1 start tasks, name:pay, id:6 end tasks, name:pay, id:6 start func task_type1 start tasks, name:pay, id:7 end tasks, name:pay, id:7 start func task_type1 start tasks, name:pay, id:8 end tasks, name:pay, id:8 start func task_type1 start tasks, name:pay, id:9 end tasks, name:pay, id:9 start func task_type1 start tasks, name:pay, id:10 end tasks, name:pay, id:10 result:1 result:2 result:3 result:4 result:5 result:6 result:7 result:8 result:9 result:10
結果是拿到了,但是每次start()之后join,則子線程阻塞,知道執行結束才開始下一次循環,這樣的執行效率等同於單線程循環,如果一個線程任務執行2秒,那這樣的方式執行10個任務就要20s,顯然是錯誤的用法,因為join()的作用是: 線程同步,即主線程任務結束之后,進入阻塞狀態,一直等待其他的子線程執行結束之后,主線程在終止
