【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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章