
在Python中,想要實現多任務除了使用進程,還可以使用線程來完成,線程是實現多任務的另外一種方式。
線程是進程中執行代碼的一個分支,每個執行分支(線程)要想工作執行代碼需要cpu進行調度 ,也就是說線程是cpu調度的基本單位,每個進程至少都有一個線程,而這個線程就是我們通常說的主線程。
【資料圖】
多線程可以完成多任務
多線程效果圖:
#導入線程模塊import threading
Thread([group [, target [, name [, args [, kwargs]]]]])
group: 線程組,目前只能使用Nonetarget: 執行的目標任務名args: 以元組的方式給執行任務傳參kwargs: 以字典方式給執行任務傳參name: 線程名,一般不用設置啟動線程使用start方法
import threadingimport time# 唱歌任務def sing(): # 擴展: 獲取當前線程 # print("sing當前執行的線程為:", threading.current_thread()) for i in range(3): print("正在唱歌...%d" % i) time.sleep(1)# 跳舞任務def dance(): # 擴展: 獲取當前線程 # print("dance當前執行的線程為:", threading.current_thread()) for i in range(3): print("正在跳舞...%d" % i) time.sleep(1)if __name__ == "__main__": # 擴展: 獲取當前線程 # print("當前執行的線程為:", threading.current_thread()) # 創建唱歌的線程 # target: 線程執行的函數名 sing_thread = threading.Thread(target=sing) # 創建跳舞的線程 dance_thread = threading.Thread(target=dance) # 開啟線程 sing_thread.start() dance_thread.start()執行結果:正在唱歌...0正在跳舞...0正在唱歌...1正在跳舞...1正在唱歌...2正在跳舞...2
前面我們使用線程執行的任務是沒有參數的,假如我們使用線程執行的任務帶有參數,如何給函數傳參呢?
Thread類執行任務并給任務傳參數有兩種方式:
args 表示以元組的方式給執行任務傳參kwargs 表示以字典方式給執行任務傳參示例代碼:import threadingimport time# 帶有參數的任務def task(count): for i in range(count): print("任務執行中..") time.sleep(0.2) else: print("任務執行完成")if __name__ == "__main__": # 創建子線程 # args: 以元組的方式給任務傳入參數 sub_thread = threading.Thread(target=task, args=(5,)) sub_thread.start()執行結果:任務執行中..任務執行中..任務執行中..任務執行中..任務執行中..任務執行完成
示例代碼:import threadingimport time# 帶有參數的任務def task(count): for i in range(count): print("任務執行中..") time.sleep(0.2) else: print("任務執行完成")if __name__ == "__main__": # 創建子線程 # kwargs: 表示以字典方式傳入參數 sub_thread = threading.Thread(target=task, kwargs={"count": 3}) sub_thread.start()執行結果:任務執行中..任務執行中..任務執行中..任務執行完成
import threadingimport timedef task(): time.sleep(1) print("當前線程:", threading.current_thread().name)if __name__ == "__main__": for _ in range(5): sub_thread = threading.Thread(target=task) sub_thread.start() 執行結果:當前線程: Thread-1當前線程: Thread-2當前線程: Thread-4當前線程: Thread-5當前線程: Thread-3
說明:
線程之間執行是無序的,它是由cpu調度決定的 ,cpu調度哪個線程,哪個線程就先執行,沒有調度的線程不能執行。進程之間執行也是無序的,它是由操作系統調度決定的,操作系統調度哪個進程,哪個進程就先執行,沒有調度的進程不能執行。假如我們現在創建一個子線程,這個子線程執行完大概需要2.5秒鐘,現在讓主線程執行1秒鐘就退出程序,查看一下執行結果,
示例代碼如下:import threadingimport time# 測試主線程是否會等待子線程執行完成以后程序再退出def show_info(): for i in range(5): print("test:", i) time.sleep(0.5)if __name__ == "__main__": sub_thread = threading.Thread(target=show_info) sub_thread.start() # 主線程延時1秒 time.sleep(1) print("over")執行結果:test: 0test: 1overtest: 2test: 3test: 4說明:通過上面代碼的執行結果,我們可以得知: 主線程會等待所有的子線程執行結束再結束假如我們就讓主線程執行1秒鐘,子線程就銷毀不再執行,那怎么辦呢?我們可以設置守護主線程守護主線程:守護主線程就是主線程退出子線程銷毀不再執行設置守護主線程有兩種方式: threading.Thread(target=show_info, daemon=True) 線程對象.setDaemon(True)設置守護主線程的示例代碼:import threadingimport time# 測試主線程是否會等待子線程執行完成以后程序再退出def show_info(): for i in range(5): print("test:", i) time.sleep(0.5)if __name__ == "__main__": # 創建子線程守護主線程 # daemnotallow=True 守護主線程 # 守護主線程方式1 sub_thread = threading.Thread(target=show_info, daemon=True) # 設置成為守護主線程,主線程退出后子線程直接銷毀不再執行子線程的代碼 # 守護主線程方式2 # sub_thread.setDaemon(True) sub_thread.start() # 主線程延時1秒 time.sleep(1) print("over")執行結果:test: 0test: 1over
需求:
定義一個列表類型的全局變量創建兩個子線程分別執行向全局變量添加數據的任務和向全局變量讀取數據的任務查看線程之間是否共享全局變量數據import threadingimport time# 定義全局變量my_list = list()# 寫入數據任務def write_data(): for i in range(5): my_list.append(i) time.sleep(0.1) print("write_data:", my_list)# 讀取數據任務def read_data(): print("read_data:", my_list)if __name__ == "__main__": # 創建寫入數據的線程 write_thread = threading.Thread(target=write_data) # 創建讀取數據的線程 read_thread = threading.Thread(target=read_data) write_thread.start() # 延時 # time.sleep(1) # 主線程等待寫入線程執行完成以后代碼在繼續往下執行 write_thread.join() print("開始讀取數據啦") read_thread.start() 執行結果:write_data: [0, 1, 2, 3, 4]開始讀取數據啦read_data: [0, 1, 2, 3, 4]
需求:
定義兩個函數,實現循環100萬次,每循環一次給全局變量加1創建兩個子線程執行對應的兩個函數,查看計算后的結果import threading# 定義全局變量g_num = 0# 循環一次給全局變量加1def sum_num1(): for i in range(1000000): global g_num g_num += 1 print("sum1:", g_num)# 循環一次給全局變量加1def sum_num2(): for i in range(1000000): global g_num g_num += 1 print("sum2:", g_num)if __name__ == "__main__": # 創建兩個線程 first_thread = threading.Thread(target=sum_num1) second_thread = threading.Thread(target=sum_num2) # 啟動線程 first_thread.start() # 啟動線程 second_thread.start() 執行結果:sum1: 1210949sum2: 1496035注意點:多線程同時對全局變量操作數據發生了錯誤錯誤分析:兩個線程first_thread和second_thread都要對全局變量g_num(默認是0)進行加1運算,但是由于是多線程同時操作,有可能出現下面情況:1. 在g_num=0時,first_thread取得g_num=0。此時系統把first_thread調度為”sleeping”狀態,把second_thread轉換為”running”狀態,t2也獲得g_num=02. 然后second_thread對得到的值進行加1并賦給g_num,使得g_num=13. 然后系統又把second_thread調度為”sleeping”,把first_thread轉為”running”。線程t1又把它之前得到的0加1后賦值給g_num。4. 這樣導致雖然first_thread和first_thread都對g_num加1,但結果仍然是g_num=1全局變量數據錯誤的解決辦法:線程同步: 保證同一時刻只能有一個線程去操作全局變量 同步: 就是協同步調,按預定的先后次序進行運行。如:你說完,我再說, 好比現實生活中的對講機線程同步的方式:1. 線程等待(join)2. 互斥鎖線程等待的示例代碼:import threading# 定義全局變量g_num = 0# 循環1000000次每次給全局變量加1def sum_num1(): for i in range(1000000): global g_num g_num += 1 print("sum1:", g_num)# 循環1000000次每次給全局變量加1def sum_num2(): for i in range(1000000): global g_num g_num += 1 print("sum2:", g_num)if __name__ == "__main__": # 創建兩個線程 first_thread = threading.Thread(target=sum_num1) second_thread = threading.Thread(target=sum_num2) # 啟動線程 first_thread.start() # 主線程等待第一個線程執行完成以后代碼再繼續執行,讓其執行第二個線程 # 線程同步: 一個任務執行完成以后另外一個任務才能執行,同一個時刻只有一個任務在執行 first_thread.join() # 啟動線程 second_thread.start() 執行結果:sum1: 1000000sum2: 2000000
互斥鎖: 對共享數據進行鎖定,保證同一時刻只能有一個線程去操作。
注意:
互斥鎖是多個線程一起去搶,搶到鎖的線程先執行,沒有搶到鎖的線程需要等待,等互斥鎖使用完釋放后,其它等待的線程再去搶這個鎖。threading模塊中定義了Lock變量,這個變量本質上是一個函數,通過調用這個函數可以獲取一把互斥鎖。
互斥鎖使用步驟:
# 創建鎖mutex = threading.Lock()# 上鎖mutex.acquire()...這里編寫代碼能保證同一時刻只能有一個線程去操作, 對共享數據進行鎖定...# 釋放鎖mutex.release()
注意點:
acquire和release方法之間的代碼同一時刻只能有一個線程去操作如果在調用acquire方法的時候 其他線程已經使用了這個互斥鎖,那么此時acquire方法會堵塞,直到這個互斥鎖釋放后才能再次上鎖。import threading# 定義全局變量g_num = 0# 創建全局互斥鎖lock = threading.Lock()# 循環一次給全局變量加1def sum_num1(): # 上鎖 lock.acquire() for i in range(1000000): global g_num g_num += 1 print("sum1:", g_num) # 釋放鎖 lock.release()# 循環一次給全局變量加1def sum_num2(): # 上鎖 lock.acquire() for i in range(1000000): global g_num g_num += 1 print("sum2:", g_num) # 釋放鎖 lock.release()if __name__ == "__main__": # 創建兩個線程 first_thread = threading.Thread(target=sum_num1) second_thread = threading.Thread(target=sum_num2) # 啟動線程 first_thread.start() second_thread.start() # 提示:加上互斥鎖,那個線程搶到這個鎖我們決定不了,那線程搶到鎖那個線程先執行,沒有搶到的線程需要等待 # 加上互斥鎖多任務瞬間變成單任務,性能會下降,也就是說同一時刻只能有一個線程去執行 執行結果:sum1: 1000000sum2: 2000000說明:通過執行結果可以地址互斥鎖能夠保證多個線程訪問共享數據不會出現數據錯誤問題
死鎖: 一直等待對方釋放鎖的情景就是死鎖
說明:
現實社會中,男女雙方一直等待對方先道歉的這種行為就好比是死鎖。
死鎖的結果
會造成應用程序的停止響應,不能再處理其它任務了。需求:
根據下標在列表中取值, 保證同一時刻只能有一個線程去取值
import threadingimport time# 創建互斥鎖lock = threading.Lock()# 根據下標去取值, 保證同一時刻只能有一個線程去取值def get_value(index): # 上鎖 lock.acquire() print(threading.current_thread()) my_list = [3,6,8,1] # 判斷下標釋放越界 if index >= len(my_list): print("下標越界:", index) return value = my_list[index] print(value) time.sleep(0.2) # 釋放鎖 lock.release()if __name__ == "__main__": # 模擬大量線程去執行取值操作 for i in range(30): sub_thread = threading.Thread(target=get_value, args=(i,)) sub_thread.start()
import threadingimport time# 創建互斥鎖lock = threading.Lock()# 根據下標去取值, 保證同一時刻只能有一個線程去取值def get_value(index): # 上鎖 lock.acquire() print(threading.current_thread()) my_list = [3,6,8,1] if index >= len(my_list): print("下標越界:", index) # 當下標越界需要釋放鎖,讓后面的線程還可以取值 lock.release() return value = my_list[index] print(value) time.sleep(0.2) # 釋放鎖 lock.release()if __name__ == "__main__": # 模擬大量線程去執行取值操作 for i in range(30): sub_thread = threading.Thread(target=get_value, args=(i,)) sub_thread.start()