併發程式設計中死鎖、遞迴鎖、程式/執行緒池、協程TCP伺服器併發等知識點

NQ31發表於2020-09-16

1、死鎖

定義; 類似兩個人分別被囚禁在兩間房子裡,A手上拿著的是B囚禁房間的鑰匙,而B拿著A的鑰匙,兩個人都沒法出去,沒法給對方開鎖,進而造成死鎖現象。
具體例子程式碼如下:

# -*-coding:utf-8 -*-
from threading import Thread,Lock,RLock
import time
muxeA=Lock()
muxeB=Lock()

class  MyThread(Thread):
    def run(self):
        self.func1()
        self.func2()

    def func1(self):
        muxeA.acquire()
        print('%s搶到A鎖了'%self.name)
        muxeB.acquire()
        print('%s搶到B鎖了' % self.name)
        muxeB.release()
        muxeA.release()

    def func2(self):
        muxeB.acquire()
        print('%s搶到B鎖了' % self.name)
        time.sleep(2)
        muxeA.acquire()
        print('%s搶到A鎖了' % self.name)
        muxeA.release()
        muxeB.release()

if __name__ == '__main__':
    for i in range(10):
        t=MyThread()
        t.start()
分析:
當執行func1的時候,第一個人(a)先搶到鎖A,這時候,
其他人只能繼續等待搶鎖A,沒有人跟a搶鎖B,所以a也搶到鎖B,
拿到鎖B後,a就把鎖B先釋放掉,再釋放鎖A,
繼續執行func2,a也順利搶到鎖B,這時候,睡眠2s,
而鎖A已經被第二個人搶到了,這時候,第二個人只拿到鎖A,沒有拿到鎖B,
所以沒有釋放鎖A,而a一直在等待搶鎖A,沒有釋放鎖B,
所以這時候就造成等待死迴圈的情況。

執行結果如下:

在這裡插入圖片描述

2、遞迴鎖:

遞迴鎖使用:from reading import RLock

遞迴鎖的特點:
1、可以被連續的acquire和release
2、但是,只能第一個搶到這把鎖執行上述操作
3、它內部有一個計數器,每acquire一次計數加一,每realse一次計數減一
4、只要計數不為0,那麼其他人都無法搶到該鎖

具體例子程式碼如下:

# -*-coding:utf-8 -*-
from threading import Thread,RLock
import time

muxeA=muxeB=RLock()

class  MyThread(Thread):
    def run(self):
        self.func1()
        self.func2()

    def func1(self):
        muxeA.acquire()
        print('%s搶到A鎖了'%self.name)
        muxeB.acquire()
        print('%s搶到B鎖了' % self.name)
        muxeB.release()
        muxeA.release()

    def func2(self):
        muxeB.acquire()
        print('%s搶到B鎖了' % self.name)
        time.sleep(2)
        muxeA.acquire()
        print('%s搶到A鎖了' % self.name)
        muxeA.release()
        muxeB.release()

if __name__ == '__main__':
    for i in range(10):
        t=MyThread()
        t.start()

執行結果如下(沒有出現死鎖現象):
在這裡插入圖片描述

3、訊號量:
定義:就相當於多個互斥鎖:
具體例子如下:

from threading import Thread,Semaphore
import time,random
s=Semaphore(5)

def task(name):
    s.acquire()
    print('%s號停車位正在停車'%name)
    time.sleep(random.randint(1,5))
    s.release()

if __name__ == '__main__':
    for i in range(10):
        t=Thread(target=task,args=(i,))
        t.start()

 

4、事件

比如:一些執行緒需要等待另一些執行緒完成才可以操作, 就類似傳送訊號一樣。

from threading import Thread,Event
import time,random
event=Event()
def light():
    print("紅燈")
    time.sleep(3)
    print('綠燈')
    event.set() #傳送訊號

def car(name):
    print('%s車正在等紅燈'%name)
    event.wait()  #等待對方發訊號過來
    print('%s車過綠燈'%name)

if __name__ == '__main__':
    t=Thread(target=light)
    t.start()
    for i in range(10):
        t1=Thread(target=car,args=(i,))
        t1.start()

 

5、執行緒池、程式池:

池的概念:就是保證計算硬體的安全前提下,最大限度的利用計算機。
它降低執行效率,但是,保證了計算機硬體的安全。
注意: 池裡面原有的執行緒或程式是不會重複出現建立和銷燬的過程。

#執行緒池的建立:
pool=ThreadPoolExecutor()
#括號內為執行緒池中程式的個數,你可以自己設定,預設是5個,最大不會超過32個
ThreadPoolExecutor類中的引數max_workers就是池中執行緒數:初始設定程式碼如下:
max_workers = min(32, (os.cpu_count() or 1) + 4)

#程式池的建立:
pool=ProcessPoolExecutor()
#它的引數如下:os.cpu_count()表示電腦的CPU核數
self._max_workers = os.cpu_count() or 1

總結:關鍵程式碼如下:
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
pool=ProcessPoolExecutor()
pool=ThreadPoolExecutor(5)
res=pool.submit(task,i).add_done_callback(call_back)

 

程式/執行緒池的例子如下:

# -*-coding:utf-8 -*-
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
import time,os

# pool=ThreadPoolExecutor(5)
pool=ProcessPoolExecutor()

def call_back(n):
    print(">>:",n.result())
def task(n):
    print(n,os.getpid())
    time.sleep(2)
    return n*n


if __name__ == '__main__':
    # list_pool=[]
    for i in range(10):
        res=pool.submit(task,i).add_done_callback(call_back)

 

6、協程、gevent模組:

實質:就是一個思想,它是由程式設計師自己在程式碼層面上檢測IO操作,一旦遇到IO操作就會在程式碼級別上完成切換
注意:IO操作下的切換才是提高效率的,非IO操作下的切換會降低效率。

gevent模組的使用需要打猴子補丁,不然不會檢測到像time.sIeep()等O操作

#猴子補丁
from gevent import monkey
monkey.patch_all()

 

具體例子如下:

# -*-coding:utf-8 -*-
#猴子補丁
from gevent import monkey
monkey.patch_all()

from gevent import spawn
import time


def ha():
    print('hahaha')
    time.sleep(2)
    print('hahaha')

def xixi():
    print('xixi')
    time.sleep(3)
    print('xixi')

if __name__ == '__main__':
    start_time=time.time()
    g1=spawn(ha)
    g2=spawn(xixi)
    g1.join()
    g2.join()
    print(time.time()-start_time)

7、基於協程的TCP併發:

服務端例子如下:

 1 # -*-coding:utf-8 -*-
 2 #猴子補丁
 3 from gevent import monkey;monkey.patch_all()
 4 
 5 from gevent import spawn
 6 import socket
 7 
 8 def new_server(ip,addr):
 9     server=socket.socket()
10     server.bind((ip,addr))
11     server.listen(5)
12     while True:
13         conn,addr=server.accept()
14         spawn(connect,conn)
15 
16 def connect(conn):
17     while True:
18         try:
19             data=conn.recv(1024)
20             print(data.decode())
21             if len(data)==0:break
22             conn.send(data.upper())
23         except Exception as e:
24             print(e)
25             break
26 if __name__ == '__main__':
27     g1=spawn(new_server,'localhost',8080)
28     g1.join()

客戶端程式碼如下:

 1 # -*-coding:utf-8 -*-
 2 import socket
 3 from threading import Thread,current_thread
 4 
 5 def new_client():
 6     c=socket.socket()
 7     c.connect(('localhost',8080))
 8     while True:
 9         data=('你好,我是%s'%current_thread().name)
10         c.send(data.encode())
11         data1=c.recv(1024)
12         print(data)
13 
14 if __name__ == '__main__':
15     for i in range(200):
16         t=Thread(target=new_client)
17         t.start()

 

相關文章