当多个进程对一个共享的变量进行读写操作时,为了保证运行结果的正确性,通常需要对进程之间进行同步。当然,同步会降低并发的程度。常见的同步方式有: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