多進程:
優點:可以用多核
缺點:開銷大
多線程:
優點:開銷小
缺點:不能使用多核
在日常的生活中,我們用到的肯定是多核機器,所以我們只考慮多核的情況,你會說那么根據上面的優缺點,那肯定就用多進程就好了。歐克,那只是你自己的意淫而已,接下來我要解釋一波了,請聽好:
我們首先確定的點,就是在一個多核的機器上,進行一系列的操作,那么使用多線程好呢?還是多進程好呢?
在這個時候我要提出一個觀點:就是CPU肯定是用來做計算的,這是毋庸置疑的。
ok,我們在之前的基礎上,考慮兩種情況:
1,計算密集的操作:我用代碼來征服你們,看哪一個更好
from multiprocessing import Process import time def work(): res = 0 for i in range(11111100): res+=i if __name__ == '__main__': start = time.time() l = [] for i in range(4): p = Process(target=work) l.append(p) p.start() for j in l: j.join() end = time.time() print('%s'%(end - start)) 運行時間:1.794102430343628 #根據機子的不同可能結果也並不同
from threading import Thread import time def work(): res = 0 for i in range(11111100): res+=i if __name__ == '__main__': start = time.time() l = [] for i in range(4): T = Thread(target=work) l.append(T) T.start() for j in l: j.join() end = time.time() print('%s'%(end - start)) 結果:3.125178813934326
看結果一目了然,進程很快,你很牛逼,我來說說原理:對於多線程來說,上面已經提到,他的缺點就是無法使用多核,由於gil鎖的存在,他只能一個一個的取搶鎖,所以會慢,多進程則相反
2,i/o密集的操作:依舊用代碼來征服你:
from threading import Thread import time def work(): time.sleep(2) if __name__ == '__main__': start = time.time() l = [] for i in range(400): p = Thread(target=work) # p = Process(target=work) l.append(p) p.start() for j in l: j.join() end = time.time() print('%s' % (end - start)) 結果:2.048117160797119
from multiprocessing import Process import time def work(): time.sleep(2) if __name__ == '__main__': start = time.time() l = [] for i in range(400): # p = Thread(target=work) p = Process(target=work) l.append(p) p.start() for j in l: j.join() end = time.time() print('%s' % (end - start)) 結果:from multiprocessing import Process from threading import Thread import time def work(): time.sleep(2) if __name__ == '__main__': start = time.time() l = [] for i in range(400): # p = Thread(target=work) p = Process(target=work) l.append(p) p.start() for j in l: j.join() end = time.time() print('%s' % (end - start)) 結果:19.68112564086914
看結果很明顯:我用時間的停留模擬i/o阻塞,進程確實是並發的,但是在i/o阻塞的時候都要等着,無法運行,並且在進程創建的時候開銷大,時間長,即使是並發的,我開了400個進程,機已經多出了那么多的時間,可想而知,開更多會是什么樣。
應用:
計算密集 多進程 金融領域
i/o密集 多線程 爬蟲 web socket
死鎖 與遞歸鎖、
所謂死鎖: 是指兩個或兩個以上的進程或線程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱為死鎖進程,情況如下:
from threading import Thread,Lock import time mutexA = Lock() mutexB = Lock() class Work(Thread): def run(self): self.f1() self.f2() def f1(self): mutexA.acquire() print('%s 拿到了A鎖 ' % self.name) mutexB.acquire() print('%s拿到了B鎖' % self.name) mutexB.release() print('%s 釋放了 B鎖' % self.name) mutexA.release() print('%s 釋放了 A鎖' % self.name) def f2(self): mutexB.acquire() time.sleep(2) print('%s 拿到了B鎖 ' % self.name) mutexA.acquire() print('%s拿到了A鎖' % self.name) mutexA.release() print('%s 釋放了 A鎖' % self.name) mutexB.release() print('%s 釋放了 B鎖' % self.name) if __name__ == '__main__': for i in range(5): t = Work() t.start()
解決方法:
遞歸鎖:Rlock
Rlock內部有一個count 初始為0 ,鎖一下加1,釋放了就減一。
from threading import Thread,RLock import time mutexA = mutexB = RLock() class Work(Thread): def run(self): self.f1() self.f2() def f1(self): mutexA.acquire() print('%s 拿到了A鎖 ' % self.name) mutexB.acquire() print('%s拿到了B鎖' % self.name) mutexB.release() print('%s 釋放了 b鎖' % self.name) mutexA.release() print('%s 釋放了 a鎖' % self.name) def f2(self): mutexB.acquire() time.sleep(2) print('%s 拿到了A鎖 ' % self.name) mutexA.acquire() print('%s拿到了B鎖' % self.name) mutexA.release() print('%s 釋放了 b鎖' % self.name) mutexB.release() print('%s 釋放了 a鎖' % self.name) if __name__ == '__main__': for i in range(5): t = Work() t.start()
信號量Semaphore 他是一種鎖
Semaphore ()括號內的參數是幾,就說明可以有幾個可以用這段鎖中的代碼,並不是同事哦 誰先運行完,下一個就會進來。
信號量與進程池是有着同一種的用途,但是用法不同,很且差很多
from threading import Thread, Semaphore, currentThread import time,random sm = Semaphore(5) def work(): sm.acquire() print('\033[32m%s 正在上廁所\033[0m ' % currentThread().name) time.sleep(random.randint(1, 3)) print('\033[30%s 走出了廁所\033[0m' % currentThread().name) sm.release() if __name__ == '__main__': for i in range(20): t = Thread(target=work,) t.start()
Event
線程的一個關鍵特性是每個線程都是獨立運行且狀態不可預測。如果程序中的其 他線程需要通過判斷某個線程的狀態來確定自己下一步的操作,這時線程同步問題就會變得非常棘手。為了解決這些問題,我們需要使用threading庫中的Event對象。 對象包含一個可由線程設置的信號標志,它允許線程等待某些事件的發生。在 初始情況下,Event對象中的信號標志被設置為假。如果有線程等待一個Event對象, 而這個Event對象的標志為假,那么這個線程將會被一直阻塞直至該標志為真。一個線程如果將一個Event對象的信號標志設置為真,它將喚醒所有等待這個Event對象的線程。如果一個線程等待一個已經被設置為真的Event對象,那么它將忽略這個事件, 繼續執行
Event的一些方法:
event = Event()
event.isSet():返回event的狀態值; event.wait():如果 event.isSet()==False將阻塞線程; event.set(): 設置event的狀態值為True,所有阻塞池的線程激活進入就緒狀態, 等待操作系統調度; event.clear():恢復event的狀態值為False。
具體應用在mysql中
from threading import Thread, Event, currentThread import time def check_mysql(): print('%s 正在檢測mysql... ' % currentThread().name) time.sleep(4) e.set() def conn_mysql(): count = 1 while not e.is_set(): if count > 4: raise ConnectionResetError('鏈接次數過多') print('%s 正在等待第%s鏈接mysql' % (currentThread().name, count)) e.wait(timeout=1) count += 1 print('%s 連接成功' % currentThread().name) if __name__ == '__main__': traffic = Thread(target=check_mysql) traffic.start() for i in range(3): t = Thread(target=conn_mysql) t.start()
定時器
from threading import Timer def hello(): print("hello, world") t = Timer(1, hello) t.start() # after 1 seconds, "hello, world" will be printed
線程queue
import queue # q=queue.Queue(3) #先進先出 # q.put('first') # q.put('second') # q.put('third') # # q.put('fourth') # # print(q.get()) # print(q.get()) # print(q.get())
# q=queue.LifoQueue() #先進后出 # q.put('first') # q.put('second') # q.put('third') # # q.put('fourth') # # print(q.get()) # print(q.get()) # print(q.get())
import queue q=queue.PriorityQueue() #put進入一個元組,元組的第一個元素是優先級(通常是數字,也可以是非數字之間的比較),數字越小優先級越高 q.put((20,'a')) q.put((10,'b')) q.put((30,'c')) print(q.get()) print(q.get()) print(q.get())
