python综合学习一之多线程
一、python文件命名
在python文件命名時,一定要注意不能和系統默認的模塊名沖突,否則會報錯。
如下面的例子,在學習線程時,將文件名命名為 threading.py,Python腳本完全正常沒問題,結果報下面的錯誤:AttributeError: 'module' object has no attribute 'xxx'。
threading.py
# -*- coding:utf-8 -*-""" @author: Corwien @file: threading_test.py @time: 18/8/25 09:14 """import threading# 獲取已激活的線程數 print(threading.active_count())執行:
? baseLearn python threading/threading.py Traceback (most recent call last):File "threading/threading.py", line 9, in <module>import threadingFile "/Users/kaiyiwang/Code/python/baseLearn/threading/threading.py", line 12, in <module>print(threading.active_count()) AttributeError: 'module' object has no attribute 'active_count' ? baseLearn問題定位:
查看import庫的源文件,發現源文件存在且沒有錯誤,同時存在源文件的.pyc文件
問題解決:
- 1.命名py腳本時,不要與python預留字,模塊名等相同
- 2.刪除該庫的.pyc文件(因為py腳本每次運行時均會生成.pyc文件;在已經生成.pyc文件的情況下,若代碼不更新,運行時依舊會走pyc,所以要刪除.pyc文件),重新運行代碼;或者找一個可以運行代碼的環境,拷貝替換當前機器的.pyc文件即可
將腳本文件名重新命名為threading_test.py,然后執行,就不會報錯了。
? baseLearn python threading/threading_test.py 1 ? baseLearn二、多線程threading
多線程是加速程序計算的有效方式,Python的多線程模塊threading上手快速簡單,從這節開始我們就教大家如何使用它。
1、添加線程
threading_test.py
# -*- coding:utf-8 -*-""" @author: Corwien @file: threading_test.py @time: 18/8/25 09:14 """import threading# 獲取已激活的線程數 # print(threading.active_count())# 查看所有線程信息 # print(threading.enumerate())# 查看現在正在運行的線程 # print(threading.current_thread())def thread_job():print('This is a thread of %s' % threading.current_thread())def main():thread = threading.Thread(target=thread_job,) # 定義線程thread.start() # 讓線程開始工作if __name__ == '__main__':main()2、join功能
不加 join() 的結果
我們讓 T1 線程工作的耗時增加
threading_join.py
# -*- coding:utf-8 -*-""" @author: Corwien @file: threading_join.py @time: 18/8/25 09:14 """import threading import timedef thread_job():print('T1 start\n')for i in range(10):time.sleep(0.1) # 任務時間0.1sprint("T1 finish\n")def main():added_thread = threading.Thread(target=thread_job, name='T1') # 定義線程added_thread.start() # 讓線程開始工作print("all done\n")if __name__ == '__main__':main()預想中輸出的結果是按照順序依次往下執行:
T1 start T1 finish all done但實際運行結果為:
? baseLearn python threading/threading_join.py T1 start all doneT1 finish? baseLearn加入join()的結果
線程任務還未完成便輸出all done。如果要遵循順序,可以在啟動線程后對它調用join:
added_thread.start() added_thread.join() print("all done\n")打印結果:
? baseLearn python threading/threading_join.py T1 startT1 finishall done完整腳本文件:
# -*- coding:utf-8 -*-""" @author: Corwien @file: threading_join.py @time: 18/8/25 09:14 """import threading import timedef thread_job():print('T1 start\n')for i in range(10):time.sleep(0.1) # 任務時間0.1sprint("T1 finish\n")def main():added_thread = threading.Thread(target=thread_job, name='T1') # 定義線程added_thread.start() # 讓線程開始工作added_thread.join()print("all done\n")if __name__ == '__main__':main()小試牛刀
如果添加兩個線程,打印的輸出結果是怎樣的呢?
# -*- coding:utf-8 -*-""" @author: Corwien @file: threading_join.py @time: 18/8/25 09:14 """import threading import timedef T1_job():print('T1 start\n')for i in range(10):time.sleep(0.1) # 任務時間0.1sprint("T1 finish\n")def T2_job():print("T2 start\n")print("T2 finish\n")def main():thread_1 = threading.Thread(target=T1_job, name='T1') # 定義線程thread_2 = threading.Thread(target=T2_job, name='T2') # 定義線程thread_1.start() # 開啟T1thread_2.start() # 開啟T2print("all done\n")if __name__ == '__main__':main()輸出的”一種”結果是:
T1 startT2 startT2 finishall doneT1 finish現在T1和T2都沒有join,注意這里說”一種”是因為all done的出現完全取決于兩個線程的執行速度, 完全有可能T2 finish出現在all done之后。這種雜亂的執行方式是我們不能忍受的,因此要使用join加以控制。
我們試試在T1啟動后,T2啟動前加上thread_1.join():
thread_1.start() thread_1.join() # notice the difference! thread_2.start() print("all done\n")打印結果:
T1 startT1 finishT2 start all doneT2 finish可以看到,T2會等待T1結束后才開始運行。
3、儲存進程結果Queue
實現功能
代碼實現功能,將數據列表中的數據傳入,使用四個線程處理,將結果保存在Queue中,線程執行完后,從Queue中獲取存儲的結果
在多線程函數中定義一個Queue,用來保存返回值,代替return,定義一個多線程列表,初始化一個多維數據列表,用來處理:
threading_queue.py
# -*- coding:utf-8 -*-""" @author: Corwien @file: threading_queue.py @time: 18/8/25 09:14 """import threading import time from queue import Queuedef job(l, q):for i in range(len(l)):l[i] = l[i] ** 2q.put(l) #多線程調用的函數不能用return返回值def multithreading():q = Queue() #q中存放返回值,代替return的返回值threads = []data = [[1,2,3],[3,4,5],[4,4,4],[5,5,5]]for i in range(4): #定義四個線程t = threading.Thread(target=job, args=(data[i], q)) #Thread首字母要大寫,被調用的job函數沒有括號,只是一個索引,參數在后面t.start() #開始線程threads.append(t) #把每個線程append到線程列表中for thread in threads:thread.join()results = []for _ in range(4):results.append(q.get()) #q.get()按順序從q中拿出一個值print(results)if __name__ == '__main__':multithreading()執行上邊的腳本出現了這樣的錯誤:
? baseLearn python threading/threading_queue.py Traceback (most recent call last):File "threading/threading_queue.py", line 11, in <module>from queue import Queue ImportError: No module named queue查了下原因,是因為python版本導致的:
解決方法:No module named 'Queue'
On Python 2, the module is named Queue, on Python 3, it was renamed to follow PEP8 guidelines (all lowercase for module names), making it queue. The class remains Queue on all versions (following PEP8).
Typically, the way you'd write version portable imports would be to do:
python3 中這樣引用:
try:import queue except ImportError:import Queue as queue在 python2 中 我們可以這樣引用:
from Queue import Queue打印:
baseLearn python ./threading/threading_queue.py [[1, 4, 9], [9, 16, 25], [16, 16, 16], [25, 25, 25]]完整代碼:
threading_queue.py
4、GIL效率問題
何為 GIL?
這次我們來看看為什么說 python 的多線程 threading 有時候并不是特別理想. 最主要的原因是就是, Python 的設計上, 有一個必要的環節, 就是 Global Interpreter Lock (GIL)。 這個東西讓 Python 還是一次性只能處理一個東西。
GIL的解釋:
盡管Python完全支持多線程編程, 但是解釋器的C語言實現部分在完全并行執行時并不是線程安全的。 實際上,解釋器被一個全局解釋器鎖保護著,它確保任何時候都只有一個Python線程執行。 GIL最大的問題就是Python的多線程程序并不能利用多核CPU的優勢 (比如一個使用了多個線程的計算密集型程序只會在一個單CPU上面運行)。 在討論普通的GIL之前,有一點要強調的是GIL只會影響到那些嚴重依賴CPU的程序(比如計算型的)。 如果你的程序大部分只會涉及到I/O,比如網絡交互,那么使用多線程就很合適, 因為它們大部分時間都在等待。實際上,你完全可以放心的創建幾千個Python線程, 現代操作系統運行這么多線程沒有任何壓力,沒啥可擔心的。測試GIL
我們創建一個 job, 分別用 threading 和 一般的方式執行這段程序. 并且創建一個 list 來存放我們要處理的數據. 在 Normal 的時候, 我們這個 list 擴展4倍, 在 threading 的時候, 我們建立4個線程, 并對運行時間進行對比.
threading_gil.py
# -*- coding:utf-8 -*-""" @author: Corwien @file: threading_gil.py @time: 18/8/25 09:14 """import threading from Queue import Queue import copy import timedef job(l, q):res = sum(l)q.put(l) #多線程調用的函數不能用return返回值def multithreading(l):q = Queue() #q中存放返回值,代替return的返回值threads = []for i in range(4): #定義四個線程t = threading.Thread(target=job, args=(copy.copy(l), q), name="T%i" % i) #Thread首字母要大寫,被調用的job函數沒有括號,只是一個索引,參數在后面t.start() #開始線程threads.append(t) #把每個線程append到線程列表中[t.join() for t in threads]total = 0for _ in range(4):total = q.get() #q.get()按順序從q中拿出一個值print(total)def normal(l):total = sum(l)print(total)if __name__ == '__main__':l = list(range(1000000))s_t = time.time()normal(l*4)print('normal:', time.time() - s_t)s_t = time.time()multithreading(l)print('multithreading: ', time.time() - s_t)如果你成功運行整套程序, 你大概會有這樣的輸出. 我們的運算結果沒錯, 所以程序 threading 和 Normal 運行了一樣多次的運算. 但是我們發現 threading 卻沒有快多少, 按理來說, 我們預期會要快3-4倍, 因為有建立4個線程, 但是并沒有. 這就是其中的 GIL 在作怪.
1999998000000 normal: 0.10034608840942383 1999998000000 multithreading: 0.084214925765991215、線程鎖Lock
不使用 Lock 的情況
threading_lock.py
# -*- coding:utf-8 -*-""" @author: Corwien @file: threading_lock.py @time: 18/8/25 09:14 """import threading# 全局變量A的值每次加1,循環10次,并打印 def job1():global Afor i in range(10):A+=1print('job1',A)# 全局變量A的值每次加10,循環10次,并打印 def job2():global Afor i in range(10):A+=10print('job2',A)# 定義兩個線程,分別執行函數一和函數二 if __name__== '__main__':A=0t1=threading.Thread(target=job1)t2=threading.Thread(target=job2)t1.start()t2.start()t1.join()t2.join()打印輸出數據:
? baseLearn python ./threading/threading_lock.py ('job1', ('job2'1) , (11)'job1' ('job2', 22) ('job2', 32) ('job2', 42) ('job2', 52) ('job2', 62) ('job2', 72) ('job2', 82) ('job2', 92) ('job2', 102) , 12) ('job1', 103) ('job1', 104) ('job1', 105) ('job1', 106) ('job1', 107) ('job1', 108) ('job1', 109) ('job1', 110)可以看出,打印的結果非常混亂
使用 Lock 的情況
lock在不同線程使用同一共享內存時,能夠確保線程之間互不影響,使用lock的方法是, 在每個線程執行運算修改共享內存之前,執行lock.acquire()將共享內存上鎖, 確保當前線程執行時,內存不會被其他線程訪問,執行運算完畢后,使用lock.release()將鎖打開, 保證其他的線程可以使用該共享內存。
函數一和函數二加鎖
def job1():global A,locklock.acquire()for i in range(10):A+=1print('job1',A)lock.release()def job2():global A,locklock.acquire()for i in range(10):A+=10print('job2',A)lock.release()主函數中定義一個Lock
if __name__== '__main__':lock=threading.Lock()A=0t1=threading.Thread(target=job1)t2=threading.Thread(target=job2)t1.start()t2.start()t1.join()t2.join()完整代碼:
# -*- coding:utf-8 -*-""" @author: Corwien @file: threading_lock.py @time: 18/8/25 09:14 """import threadingdef job1():global A,locklock.acquire()for i in range(10):A+=1print('job1',A)lock.release()def job2():global A,locklock.acquire()for i in range(10):A+=10print('job2',A)lock.release()if __name__== '__main__':lock = threading.Lock()A=0t1=threading.Thread(target=job1)t2=threading.Thread(target=job2)t1.start()t2.start()t1.join()t2.join()打印輸出:
? baseLearn python ./threading/threading_lock.py ('job1', 1) ('job1', 2) ('job1', 3) ('job1', 4) ('job1', 5) ('job1', 6) ('job1', 7) ('job1', 8) ('job1', 9) ('job1', 10) ('job2', 20) ('job2', 30) ('job2', 40) ('job2', 50) ('job2', 60) ('job2', 70) ('job2', 80) ('job2', 90) ('job2', 100) ('job2', 110)從打印結果來看,使用lock后,一個一個線程執行完。使用lock和不使用lock,最后打印輸出的結果是不同的。
總結
以上是生活随笔為你收集整理的python综合学习一之多线程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 百度图表插件
- 下一篇: 易百教程人工智能python修正-人工智