【Python併發】【Python多進程(四)】進程同步

當多個進程對一個共享的變量進行讀寫操作時,爲了保證運行結果的正確性,通常需要對進程之間進行同步。當然,同步會降低併發的程度。常見的同步方式有: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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章