# 多工 ### 用到多工場合: * 同時間須顯示許多資料 * 讀取model和AI運算 * 需要長時間掛在背景執行 ### 線程/進程 差異 * 多線程(Thread) 簡單講:一個人有2隻手,然後可以同時進行 * 多進程(Process) 簡單講:聯合其他人一起做 ## ## 多線程 ### 正常使用 #### 2個線程幾乎同時啟動 ```python import threading from queue import Queue #Thread 無法回傳值,所以要使用 Queue.put() 將要傳回的值存入 Queue,再用 Queue.get() 取出 import time import os # A報數 def A_Count_off(): for i in range(0,5): print(f'A : {i}') time.sleep(0.5) # B報數 def B_Count_off(): for i in range(5,10): print(f'B : {i}') time.sleep(0.5) thread_list=[A_Count_off,B_Count_off] thread_num =[] for i in range(0,len(thread_list)): thread_num.append(threading.Thread(target=thread_list[i])) for i in range(0,len(thread_num)): thread_num[i].join() ``` ### 非正常使用 #### 第2個線程要等到第1個線程運行完才會啟動 ##### 失去多線程意義 ```python # A報數 def A_Count_off(): for i in range(0,5): print(f'A : {i}') time.sleep(0.5) # B報數 def B_Count_off(): for i in range(5,10): print(f'B : {i}') time.sleep(0.5) thread_list=[A_Count_off,B_Count_off] thread_num =[] for i in range(0,len(thread_num)): thread_num[i].start() thread_num[i].join() ``` ### 攜帶參數 ```python def Count_off(code_name): print('process {} '.format(os.getpid())) print('thread {} '.format(threading.current_thread().name)) for i in range(0,5): print(f'{code_name} : {i}') time.sleep(0.5) thread_name_list=["A","B","C","D"] thread_list=[] for i in range(0,len(thread_name_list)): thread_list.append(threading.Thread(target=Count_off,args=thread_name_list[i])) # 攜帶參數 for i in range(0,len(thread_list)): thread_list[i].start() for i in range(0,len(thread_list)): thread_list[i].join() ``` ### 使用class的方式(推薦使用) #### 後續觀看程式碼及維護,會提高不少 * start會使用分出去的線程 * run會使用主線程 ```python class Thread_class(threading.Thread): def __init__(self,code_name): threading.Thread.__init__(self) self.code_name = code_name def run(self): print('process {} '.format(os.getpid())) # 查看進程 print('thread {} '.format(threading.current_thread().name)) # 查看線程 for i in range(0,5): print(f'{self.code_name} : {i}') time.sleep(0.5) def test_return(self): return(f'{self.code_name}=END') thread_name_list=["A","B","C","D"] thread_list=[] for i in range(0,len(thread_name_list)): thread_list.append(Thread_class(thread_name_list[i])) for i in range(0,len(thread_list)): thread_list[i].start() # 啟動額外線程 for i in range(0,len(thread_list)): thread_list[i].join() ``` ```python class Thread_class(threading.Thread): def __init__(self,code_name): threading.Thread.__init__(self) self.code_name = code_name def run(self): print('process {} '.format(os.getpid())) # 查看進程 print('thread {} '.format(threading.current_thread().name)) # 查看線程 for i in range(0,5): print(f'{self.code_name} : {i}') time.sleep(0.5) def test_return(self): return(f'{self.code_name}=END') thread_name_list=["A","B","C","D"] thread_list=[] for i in range(0,len(thread_name_list)): thread_list.append(Thread_class(thread_name_list[i])) for i in range(0,len(thread_list)): thread_list[i].run() # 這樣只會套用到 呼叫method,會用主線程去調用 for i in range(0,len(thread_list)): thread_list[i].join() ``` ### Qthread: * 使用方式與thread相似 * 多了信號槽使用(可以擁有多個) * 方便與主UI的線程溝通 ```python class ReadTime(QtCore.QThread): # 讀取時間 time_out = pyqtSignal(str) # 聲明一個帶字串參數的信號槽 def __init__(self, parent=None): super().__init__(parent) def run(self): while True: result = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) # 讀取當下時間 self.time_out.emit(f'{result}') # 傳送信号 self.msleep(500) # 休眠0.5秒 ``` ## ## 多進程 ### 多進程與多線程的寫法極為相似 #### 正常使用: #### 2個進程幾乎同時啟動 ```python # A報數 def A_Count_off(): for i in range(0,5): print(f'A : {i}') time.sleep(0.5) # B報數 def B_Count_off(): for i in range(5,10): print(f'B : {i}') time.sleep(0.5) if __name__=='__main__': process_list=[A_Count_off,B_Count_off] process_name =[] for i in range(0,len(process_list)): process_name.append(mp.Process(target=process_list[i])) for i in range(0,len(process_name)): process_name[i].start() for i in range(0,len(process_name)): process_name[i].join() ``` ### 非正常使用: #### 第2個進程要等到第1個進程運行完才會啟動 #### 失去多進程意義 ```python # A報數 def A_Count_off(): for i in range(0,5): print(f'A : {i}') time.sleep(0.5) # B報數 def B_Count_off(): for i in range(5,10): print(f'B : {i}') time.sleep(0.5) if __name__=='__main__': process_list=[A_Count_off,B_Count_off] process_name =[] for i in range(0,len(process_list)): process_name.append(mp.Process(target=process_list[i])) for i in range(0,len(process_name)): process_name[i].start() process_name[i].join() ``` ### 攜帶參數 ```python if __name__=='__main__': process_name_list=["A","B","C","D"] process_list=[] for i in range(0,len(process_name_list)): process_list.append(mp.Process(target=Count_off,args=process_name_list[i])) # 攜帶參數 ``` ### 使用class的方式(推薦使用) #### 後續觀看程式碼及維護,會提高不少 * start會使用分出去的進程 * run會使用主線程 * 但start後,無法直接調用此class的參數,等等會大概說明 ```python class Process_class(mp.Process): def __init__(self, code_name): mp.Process.__init__(self) self.code_name = code_name def run(self): print('process {} '.format(os.getpid())) # 查看進程 print('thread {} '.format(threading.current_thread().name)) # 查看線程 for i in range(0,5): print(f'{self.code_name} : {i}') time.sleep(0.5) def test_return(self): return(f'{self.code_name}=END') if __name__=='__main__': start_time = time.time() process_name_list = ["A", "B", "C", "D"] process_list = [] for i in range(0, len(process_name_list)): process_list.append(Process_class(process_name_list[i])) for i in range(0, len(process_list)): process_list[i].start() for i in range(0, len(process_list)): process_list[i].join() for i in range(0, len(process_list)): print(process_list[i].test_return()) ``` ### 多進程的溝通方式 #### 多進程的溝通方式需要透過Queue或pipe 參考資料 http://www.taroballz.com/2018/01/11/processing_communcation/ 多線程由於共享記憶體溝通方式比多進程方便 2者差異的參考資料 https://blog.csdn.net/Victor2code/article/details/109005171 #### 用團隊合作方式來理解,每個人都在做自己的事,若沒有溝通或是告知(Queue),則沒有辦法獲取資料