並行,並發,多線程,GIL全局解釋器鎖
串行
你吃飯吃到一半,電話來了,你一直到吃完了以後才去接,這就說明你不支持並發也不支持並行。是串行,必須執行完一個執行一個。
並發
你吃飯吃到一半,電話來了,你停了下來接了電話,接完後繼續吃飯,這說明你支持並發,交叉執行。
並行
你吃飯吃到一半,電話來了,你一邊打電話一邊吃飯,這說明你支持並行,同時執行。
並發的關鍵是你有處理多個任務的能力,不一定要同時。
並行的關鍵是你有同時處理多個任務的能力
多任務
同時幹多件事, 有幾核cpu就能同時幹幾件事
線程
最小的調度單位,線程是並發的,
多線程
多線程類似於同時執行多個不同程序,多線程運行有如下特點:
1.無序性
2.線程可以被搶占(中斷)
3.在其他線程正在運行時,線程可以暫時擱置(也稱為睡眠) -- 這就是線程的退讓。
4.線程的並發是利用cpu上下文的切換
5.多線程開銷小,比較適合在IO密集的代碼裏使用。
要設置多線程,需要調用模塊
import threading
實現多線程:
import threading #調用多線程模塊 import time def test1(): for i in range(10): time.sleep(1) print(‘test1=========>%s‘ % i) def test2(): for i in range(10): time.sleep(1) print(‘test2=========>%s‘ % i) t1 = threading.Thread(target=test1) #開啟一個多線程將test1函數傳入並起別名 t2 = threading.Thread(target=test2) t1.start() #開啟t1線程 t2.start() test1() test2()
執行結果
執行順序,——> test1 ——> test2 ——> test1 ——> test2 ——> ....
test2=========>0 test1=========>0 test2=========>1 test1=========>1 test2=========>2 test1=========>2 test1=========>3 test2=========>3 test2=========>4 test1=========>4
證明多線程的無序性
def test1(n): time.sleep print(‘task‘, n) for i in range(5): t = threading.Thread(target=test1,args=(‘t-%s‘ % i,)) 給test1函數傳入實參(‘t-%s‘ % i,),括號內最後的’,’不可缺少 t.start()
結果:無序
task t-1 task t-3 task t-0 task t-2 task t-4
多線程共享全局變量
全局變量:
這是因為,在一個作用域裏面給一個變量賦值的時候,Python自動認為這個變量是這個作用域的本地變量,並屏蔽作用域外的同名的變量。 所以我們會得到這樣 UnboundLocalError 的一個異常。
使用全局變量的話不需要聲明,給全局變量賦值的話就需要聲明了。 一般全局變量在一個函數中是不可以調用的,但是聲明的話,就可以在函數中調用全局函數
globa 變量名 :聲明全局變量
num = 0 def update(): global num #global聲明全局變量 for i in range(10): num += 1 def reader(): global num print(num) t1 = threading.Thread(target=update) t2 = threading.Thread(target=reader) t1.start() t2.start()
結果
10
GIL全局解釋器鎖
只要在進行耗時的IO操作的時候,能釋放GIL, 所以只要在IO密集型的代碼裏,用多線程就很合適
import threading global_num = 0 def test1(): global global_num for i in range(1000000): global_num += 1 print("test1", global_num,threading.current_thread()) #打印當前線程的信息 def test2(): global global_num for i in range(1000000): global_num += 1 print("test2", global_num,threading.current_thread()) t1 = threading.Thread(target=test1) t2 = threading.Thread(target=test2) t1.start() t2.start() # t1.join() # 執行完t1在執行主函數 # t2.join() # 執行完t2再執行主函數 print(global_num)
結果:
如果註釋掉 t1.join(), t2.join() 這兩行,結果:(每一次都不一樣)
472178 test1 1151965 <Thread(Thread-1, started 14300)> test2 1279527 <Thread(Thread-2, started 4348)>
如果加上 t1.join(), t2.join() 這兩行,結果:(每次都不一樣)
主函數的結果和最後執行完的線程的結果一樣
test1 1087118 <Thread(Thread-1, started 13744)> test2 1320500 <Thread(Thread-2, started 19292)> 1320500
互斥鎖
說到gil解釋器鎖,我們容易想到在多線程中共享全局變量的時候會有線程對全局變量進行的資源競爭,會對全局變量的修改產生不是我們想要的結果,而那個時候我們用到的是python中線程模塊裏面的互斥鎖,哪樣的話每次對全局變量進行操作的時候,只有一個線程能夠拿到這個全局變量;看下面的代碼:
import threading import time global_num = 0 lock = threading.Lock() def test1(): global global_num lock.acquire() for i in range(1000000): global_num += 1 lock.release() print("test1", global_num) def test2(): global global_num lock.acquire() for i in range(1000000): global_num += 1 lock.release() print("test2", global_num) t1 = threading.Thread(target=test1) t2 = threading.Thread(target=test2) start_time = time.time() t1.start() t2.start()
結果
互斥性:不可分割,要不不做,要做做完。
加上鎖之後就變成了串行,加鎖的範圍盡量精確,範圍要縮到最小
test1 1000000 test2 2000000
並行,並發,多線程,GIL全局解釋器鎖