1. 程式人生 > >python網路程式設計之互斥鎖

python網路程式設計之互斥鎖

標籤(空格分隔): 互斥鎖


程序之間的資料不共享,但是共享同一套檔案系統,所以訪問同一個檔案,或者同一個列印終端,是沒有問題的,而共享帶來的問題就是競爭,競爭帶來的結果就是錯亂,如下:

#併發執行,效率高,但競爭同一列印終端,帶來了列印錯亂
from multiprocessing import Process
import os,time
def work():
    print('%s is running' %os.getpid())
    time.sleep(2)
    print('%s is done' %os.getpid())

if __name__ == '__main__':
    for i in range(3):
        p=Process(target=work)
        p.start()

如何控制,就是加鎖處理。而互斥鎖的意思就是互相排斥,如果把多個程序比喻為多個人,互斥鎖的工作原理就是多個人都要去爭搶同一個資源:衛生間,一個人搶到衛生間後上一把鎖,其他人都要等著,等到這個完成任務後釋放鎖,其他人才有可能有一個搶到......所以互斥鎖的原理,就是把併發改成穿行,降低了效率,但保證了資料安全不錯亂

#由併發變成了序列,犧牲了執行效率,但避免了競爭
from multiprocessing import Process,Lock
import os,time
def work(lock):
    lock.acquire() #加鎖
    print('%s is running' %os.getpid())
    time.sleep(2)
    print('%s is done' %os.getpid())
    lock.release() #釋放鎖
if __name__ == '__main__':
    lock=Lock()
    for i in range(3):
        p=Process(target=work,args=(lock,))
        p.start()

模擬搶票練習:

多個程序共享同一檔案,我們可以把檔案當資料庫,用多個程序模擬多個人執行搶票任務;

#檔案db.txt的內容為:{"count":1}
#注意一定要用雙引號,不然json無法識別
from multiprocessing import Process
import time,json

def search(name):
    dic=json.load(open('db.txt'))
    time.sleep(1)
    print('\033[43m%s 查到剩餘票數%s\033[0m' %(name,dic['count']))

def get(name):
    dic=json.load(open('db.txt'))
    time.sleep(1) #模擬讀資料的網路延遲
    if dic['count'] >0:
        dic['count']-=1
        time.sleep(1) #模擬寫資料的網路延遲
        json.dump(dic,open('db.txt','w'))
        print('\033[46m%s 購票成功\033[0m' %name)

def task(name):
    search(name)
    get(name)

if __name__ == '__main__':
    for i in range(10): #模擬併發10個客戶端搶票
        name='<路人%s>' %i
        p=Process(target=task,args=(name,))
        p.start()

併發執行,效率高,但競爭寫同一檔案,資料寫入錯亂,只有一張票,賣成功給了10個人

<路人0> 查到剩餘票數1
<路人1> 查到剩餘票數1
<路人2> 查到剩餘票數1
<路人3> 查到剩餘票數1
<路人4> 查到剩餘票數1
<路人5> 查到剩餘票數1
<路人6> 查到剩餘票數1
<路人7> 查到剩餘票數1
<路人8> 查到剩餘票數1
<路人9> 查到剩餘票數1
<路人0> 購票成功
<路人4> 購票成功
<路人1> 購票成功
<路人5> 購票成功
<路人3> 購票成功
<路人7> 購票成功
<路人2> 購票成功
<路人6> 購票成功
<路人8> 購票成功
<路人9> 購票成功

加鎖處理:購票行為由併發變成了序列,犧牲了執行效率,但保證了資料安全

#把檔案db.txt的內容重置為:{"count":1}
from multiprocessing import Process,Lock
import time,json

def search(name):
    dic=json.load(open('db.txt'))
    time.sleep(1)
    print('\033[43m%s 查到剩餘票數%s\033[0m' %(name,dic['count']))

def get(name):
    dic=json.load(open('db.txt'))
    time.sleep(1) #模擬讀資料的網路延遲
    if dic['count'] >0:
        dic['count']-=1
        time.sleep(1) #模擬寫資料的網路延遲
        json.dump(dic,open('db.txt','w'))
        print('\033[46m%s 購票成功\033[0m' %name)

def task(name,lock):
    search(name)
    with lock: #相當於lock.acquire(),執行完自程式碼塊自動執行lock.release()
        get(name)

if __name__ == '__main__':
    lock=Lock()
    for i in range(10): #模擬併發10個客戶端搶票
        name='<路人%s>' %i
        p=Process(target=task,args=(name,lock))
        p.start()

執行結果:
<路人0> 查到剩餘票數1
<路人1> 查到剩餘票數1
<路人2> 查到剩餘票數1
<路人3> 查到剩餘票數1
<路人4> 查到剩餘票數1
<路人5> 查到剩餘票數1
<路人6> 查到剩餘票數1
<路人7> 查到剩餘票數1
<路人8> 查到剩餘票數1
<路人9> 查到剩餘票數1
<路人0> 購票成功

互斥鎖與join

使用join可以將併發變成序列,互斥鎖的原理也是將併發變成穿行,那我們直接使用join就可以了啊,為何還要互斥鎖,說到這裡我趕緊試了一下

#把檔案db.txt的內容重置為:{"count":1}
from multiprocessing import Process,Lock
import time,json

def search(name):
    dic=json.load(open('db.txt'))
    print('\033[43m%s 查到剩餘票數%s\033[0m' %(name,dic['count']))

def get(name):
    dic=json.load(open('db.txt'))
    time.sleep(1) #模擬讀資料的網路延遲
    if dic['count'] >0:
        dic['count']-=1
        time.sleep(1) #模擬寫資料的網路延遲
        json.dump(dic,open('db.txt','w'))
        print('\033[46m%s 購票成功\033[0m' %name)

def task(name,):
    search(name)
    get(name)

if __name__ == '__main__':
    for i in range(10):
        name='<路人%s>' %i
        p=Process(target=task,args=(name,))
        p.start()
        p.join()

執行結果:
<路人0> 查到剩餘票數1
<路人0> 購票成功
<路人1> 查到剩餘票數0
<路人2> 查到剩餘票數0
<路人3> 查到剩餘票數0
<路人4> 查到剩餘票數0
<路人5> 查到剩餘票數0
<路人6> 查到剩餘票數0
<路人7> 查到剩餘票數0
<路人8> 查到剩餘票數0
<路人9> 查到剩餘票數0

發現使用join將併發改成穿行,確實能保證資料安全,但問題是連查票操作也變成只能一個一個人去查了,很明顯大家查票時應該是併發地去查詢而無需考慮資料準確與否,此時join與互斥鎖的區別就顯而易見了,join是將一個任務整體序列,而互斥鎖的好處則是可以將一個任務中的某一段程式碼序列,比如只讓task函式中的get任務序列

def task(name,):
    search(name) # 併發執行

    lock.acquire()
    get(name) #序列執行
    lock.release()

總結:
加鎖可以保證多個程序修改同一塊資料時,同一時間只能有一個任務可以進行修改,即序列地修改,沒錯,速度是慢了,但犧牲了速度卻保證了資料安全。

雖然可以用檔案共享資料實現程序間通訊,但問題是:

1、效率低(共享資料基於檔案,而檔案是硬碟上的資料)

2、需要自己加鎖處理

因此我們最好找尋一種解決方案能夠兼顧:

1、效率高(多個程序共享一塊記憶體的資料)

2、幫我們處理好鎖問題。

這就是mutiprocessing模組為我們提供的基於訊息的IPC通訊機制:佇列和管道。

佇列和管道都是將資料存放於記憶體中,而佇列又是基於(管道+鎖)實現的,可以讓我們從複雜的鎖問題中解脫出來,因而佇列才是程序間通訊的最佳選擇。

我們應該儘量避免使用共享資料,儘可能使用訊息傳遞和佇列,避免處理複雜的同步和鎖問題,而且在程序數目增多時,往往可以獲得更好的可獲展性。