并发编程经典问题之吸烟者。本文简单介绍了问题模型,并提供了一种 Python3 的解决方式。
问题模型
-
问题描述
- 假设一个系统有三个抽烟者进程和一个供应者进程。
- 每个抽烟者不停地卷烟并抽掉它,但是要卷起并抽掉一支烟,抽烟者需要有三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草、第二个拥有纸,第三个拥有胶水。
- 供应者进程无限地提供三种材料, 供应者每次将两种材料放到桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者一个信号告诉完成了,供应者就会放另外两种材料在桌上,这种过程一直重复(让三个抽烟者轮流地抽烟)。
-
问题拓展
- 假设供应者不并不确定有多少个抽烟者
- 供应者每次都随机的提供的两种不同的材料
-
问题分析
- 供应者与三个抽烟者分别是同步关系:所有抽烟者都空闲时才会提供一份材料;
- 三个抽烟者卷烟、抽烟和供应的动作是互斥关系;
- 有且仅有一块共享缓存区,用来存放材料;
- 有 4 个进程,供应者作为生产者向三个抽烟者提供材料;
Talk is cheap, show me code
- 下面的例子是线程的实现,用多线程去实现一个纯粹的同步关系,本身是没有意义的,因为完全可以在一个函数里面实现。
- 但是这种思想可以扩展到进程间的协作、分布式应用间的协作。
from threading import Thread,Condition,Lock,current_thread
from random import randint
from copy import deepcopy
from time import sleep
# 定义卷烟材料
RES_GLU = 0 # 胶水
RES_PAPER = 1 # 纸张
RES_TOBAC = 2 # 烟叶
RES_ALL = [RES_GLU, RES_PAPER, RES_TOBAC]
CONV = {
RES_GLU:'胶水',
RES_PAPER:'纸张',
RES_TOBAC:'烟草',
}
# 供货者
def supplier(res_con, res_desk):
print("Supplier Started", flush=True)
res_con.acquire()
while True:
res = deepcopy(RES_ALL)
loss = randint(0,2)
res.remove(loss) # 随机得到两种材料
res_desk.append(res)
print("Supplier Data Ready Except: " + CONV[loss], flush=True)
res_con.notify_all()
res_con.wait()
# 卷烟、抽烟
def make_cigar(src):
sleep(1)
# 吸烟者
waked_count = 0
def nicotian(res_con, res_desk, res):
global waked_count
th = current_thread()
print("Nicotian[" + th.name + "] with " + CONV[res] + " Started", flush=True)
res_con.acquire()
while True:
res_con.wait()
waked_count += 1
if res_desk and (res not in res_desk[0]):
make_cigar(res_desk.pop())
print("Nicotian with " + CONV[res] + " Has Smoked\n", flush=True)
if waked_count == 3:
if res_desk:
print("ERROR: 材料没有被使用", flush=True)
exit()
waked_count = 0
res_con.notify()
def main():
# 材料桌,初始为空,供应者放入材料,吸烟者取出材料
res_desk = []
# 材料状态控制
res_con = Condition(Lock())
# 吸烟者
res_list = RES_ALL # 给吸烟者分配材料
thread_nic = []
for res in res_list:
thread_nic.append(Thread(target=nicotian, args=(res_con, res_desk, res), daemon=True))
thread_nic[-1].start()
sleep(1)
# 供应者
thread_supp = Thread(target=supplier, args=(res_con, res_desk), daemon=True)
thread_supp.start()
input("\n\n===Enter Any Key To Exit===\n\n")
if __name__ == '__main__':
main()