當多個進程對一個共享的變量進行讀寫操作時,爲了保證運行結果的正確性,通常需要對進程之間進行同步。當然,同步會降低併發的程度。常見的同步方式有:Lock(鎖)、Semaphore(信號量)、Event(事件)和Condition(條件變量)。
一、Lock(鎖)
通過使用Lock來控制一段代碼在同一時間只能被一個進程執行。Lock對象的兩個方法,acquire()用來獲取鎖,release()用來釋放鎖。當一個進程調用acquire()時,如果鎖的狀態爲unlocked,那麼會立即修改爲locked並返回,這時該進程即獲得了鎖。如果鎖的狀態爲locked,那麼調用acquire()的進程則阻塞。
1.1 未使用鎖帶來的問題
下面的代碼導致3個進程的啓動順序與結束順序不一致,因此同一個時間三個線程都執行了方法work()
import os
import time
import random
from multiprocessing import Process
def work(n):
print('{}: {} is running'.format(n, os.getpid()))
time.sleep(random.random())
print('{}: {} is done'.format(n, os.getpid()))
if __name__ == '__main__':
for i in range(3):
p = Process(target=work,args=(i,))
p.start()
可能的輸出:
0: 13368 is running
1: 22356 is running
2: 22720 is running
1: 22356 is done
2: 22720 is done
0: 13368 is done
1.2 使用鎖保證進程啓動順序和結束順序一致
import os
import time
import random
from multiprocessing import Process, Lock
def work(lock, n):
lock.acquire() # 獲得鎖,只有一個進程可以獲得
print('{}: {} is running'.format(n, os.getpid()))
time.sleep(random.random())
print('{}: {} is done'.format(n, os.getpid()))
lock.release() # 釋放鎖
if __name__ == '__main__':
lock = Lock()
for i in range(3):
p = Process(target=work, args=(lock,i))
p.start()
可能的輸出:
0: 16356 is running
0: 16356 is done
2: 23500 is running
2: 23500 is done
1: 18336 is running
1: 18336 is done
二、Semaphore(信號量)
Lock只允許同一時刻有一個進程訪問鎖住的代碼段,而Semaphore則是允許一定數量的進程訪問。Semaphore在實現時會維護一個計數器,每調用一個acquire(),計數器減1,調用一次release()則計數器加1。當計數器爲0時,調用acquire()則會阻塞。
import multiprocessing
import time
def worker(s, i):
s.acquire()
print(multiprocessing.current_process().name + " acquire")
time.sleep(i)
print(multiprocessing.current_process().name + " release")
s.release()
if __name__ == "__main__":
s = multiprocessing.Semaphore(2) # 最多允許2個進程進入,否則阻塞
for i in range(5):
p = multiprocessing.Process(target=worker, args=(s, i * 2))
p.start()
可能的輸出:
Process-2 acquire
Process-1 acquire
Process-1 release
Process-3 acquire
Process-2 release
Process-4 acquire
Process-3 release
Process-5 acquire
Process-4 release
Process-5 release
輸出分析:
1,2進入,1退出3進入,2退出4進入,3退出5進入,4退出,5退出
三、Event(事件)
Event是主線程控制其他線程的方式。在Event機制中,會設置一個"Flag",如果"Flag"爲False時,那麼調用event.wait()的進程就會阻塞。當"Flag"爲True時,那些阻塞了的進程就不再阻塞。其中, set()方法用於設置"Flag"爲True,clear()則是設置"Flag"爲False。
import multiprocessing
import time
def wait_for_event(e):
"""等待event對象被設置爲True"""
print('wait_for_event: starting')
e.wait() # 如果主線程不設置event對象,那麼該進程會一直阻塞
print('wait_for_event: e.is_set()->' + str(e.is_set()))
def wait_for_event_timeout(e, t):
"""Wait t seconds and then timeout"""
print('wait_for_event_timeout: starting')
e.wait(t) # 如果主線程不設置event對象,那麼該進程會一直阻塞直至超市
print('wait_for_event_timeout: e.is_set()->' + str(e.is_set()))
if __name__ == '__main__':
e = multiprocessing.Event()
print(e.is_set()) # Event的初始狀態爲False。此時,任何調用該Event對象的wait()方法都會阻塞
w1 = multiprocessing.Process(name='block',
target=wait_for_event,
args=(e,))
w1.start()
w2 = multiprocessing.Process(name='non-block',
target=wait_for_event_timeout,
args=(e, 2))
w2.start()
time.sleep(10)
e.set()
print('main: event is set')
可能的輸出:
False
wait_for_event: starting
wait_for_event_timeout: starting
wait_for_event_timeout: e.is_set()->False
main: event is set
wait_for_event: e.is_set()->True
四、Condition(條件變量)
condition能夠實現在某些條件下才會釋放鎖。其常用方法包含:
- wait():掛起進程,收到notify()通知後繼續運行;
- notify():通知其他線程,解除其他線程中的一個線程的阻塞狀態;
- notifyall():通知其他線程,解除其他所有線程的阻塞裝;
- acquire():獲得鎖;
- release():釋放鎖;
下面是一個進程間的生產者-消費者的例子,當生產達到5個產品時開始消費,並在消費完所有產品後再進行生產。
from multiprocessing import Process, Condition, Value
import time
def product(num, con):
con.acquire()
while True:
print("開始生產.")
num.value += 1
print("產品數量:{}".format(str(num.value)))
time.sleep(1)
if num.value >= 5:
print("產生數量已達到5個,無法繼續生產")
con.notify() # 喚醒消費者
con.wait() # 阻塞,等待喚醒
con.release()
def consume(num, con):
con.acquire()
while True:
print("開始消費.")
num.value -= 1
print("產品剩餘數量:{}".format(num.value))
time.sleep(1)
if num.value <= 0:
print("產品已被消費完.")
con.notify() # 喚醒生產者
con.wait() # 阻塞,等待喚醒
con.release()
if __name__ == '__main__':
num = Value('i', 0) # 進程間共享內存
con = Condition()
producer = Process(target=product, args=(num, con))
consumer = Process(target=consume, args=(num, con))
producer.start()
consumer.start()
部分輸出:
開始生產.
產品數量:1
開始生產.
產品數量:2
開始生產.
產品數量:3
開始生產.
產品數量:4
開始生產.
產品數量:5
產生數量已達到5個,無法繼續生產
開始消費.
產品剩餘數量:4
開始消費.
產品剩餘數量:3
開始消費.
產品剩餘數量:2
開始消費.
產品剩餘數量:1
開始消費.
產品剩餘數量:0
產品已被消費完.
開始生產.
產品數量:1