进程同步控制 —— 锁\信号量\事件

锁 —— multiprocessing.Lock  

       通过刚刚的学习,我们千方百计实现了程序的异步,让多个任务可以同时在几个进程中并发处理,他们之间的运行没有顺序(或者说由操作系统调度决定他们的顺序),一旦开启也不受我们控制。尽管并发编程让我们能更加充分的利用IO资源,但是也给我们带来了新的问题。

  当多个进程使用同一份数据资源的时候,就会引发数据安全或顺序混乱问题。

  加锁的形式确保了程序的顺序执行,但是执行又变成了串行,降低了效率,但是不得不说,它确保了数据的安全性。

 

  l = Lock()

  一把钥匙配一把锁

  拿钥匙,锁门   l.acquire ( acquire: 获取 )

  还钥匙,开门   l.release (release: 释放)

from multiprocessing import Lock

l = Lock()

l.acquire()# 拿走钥匙,锁门,不让其他人进屋

l.release()# 释放锁。  还钥匙,开门,允许其他人进屋
# -*- coding:utf-8 -*-
# Author : Niuli
# Data : 2018/8/21 下午3:59
from multiprocessing import Process, Lock, Value
import time


def get_money(num, l):
    l.acquire()  # 拿走钥匙,锁门
    for i in range(100):  # 100块,每次取1块
        num.value -= 1
        print('取:', num.value)
        time.sleep(0.01)
    l.release()  # 还钥匙,开门,下一位可以进入


def put_money(num, l):
    l.acquire()  # 拿走钥匙,关上门
    for i in range(101):  # 100块,每次存1块
        num.value += 1
        print('存:', num.value)
        time.sleep(0.01)
    l.release()  # 还钥匙.开门,下一位可以进入


if __name__ == '__main__':
    num = Value('i', 100)  # 类似 struck 需要一个类型,一个数据
    l = Lock()  # 实例化Lock的对象l (需要先制造一把锁)
    p_get = Process(target=get_money, args=(num, l))  # 创建取钱的进程,传两个参数:钱数,锁
    p_put = Process(target=put_money, args=(num, l))  # 创建存钱的进程,传两个参数:钱数,锁
    p_get.start()  # 开启取钱的进程
    p_put.start()  # 开启存钱的进程
    # 没有join 会异步执行,主程序会先执行完,打印最终的钱数 会是最开始的钱数
    # 没有join会先执行完主程序
    # 有join,主程序会等着子程序执行完后再接着执行
    p_get.join()  # 异步转同步,让子进程先执行,主程序等着  
    p_put.join()  # 异步转同步,让子进程先执行,主程序等着
    print('这里是最终的钱数:', num.value)
银行存取钱问题
取: 99
取: 98
取: 97
取: 96
取: 95
取: 94
取: 93
取: 92
取: 91
取: 90
取: 89
取: 88
取: 87
取: 86
取: 85
取: 84
取: 83
取: 82
取: 81
取: 80
取: 79
取: 78
取: 77
取: 76
取: 75
取: 74
取: 73
取: 72
取: 71
取: 70
取: 69
取: 68
取: 67
取: 66
取: 65
取: 64
取: 63
取: 62
取: 61
取: 60
取: 59
取: 58
取: 57
取: 56
取: 55
取: 54
取: 53
取: 52
取: 51
取: 50
取: 49
取: 48
取: 47
取: 46
取: 45
取: 44
取: 43
取: 42
取: 41
取: 40
取: 39
取: 38
取: 37
取: 36
取: 35
取: 34
取: 33
取: 32
取: 31
取: 30
取: 29
取: 28
取: 27
取: 26
取: 25
取: 24
取: 23
取: 22
取: 21
取: 20
取: 19
取: 18
取: 17
取: 16
取: 15
取: 14
取: 13
取: 12
取: 11
取: 10
取: 9
取: 8
取: 7
取: 6
取: 5
取: 4
取: 3
取: 2
取: 1
取: 0
存: 1
存: 2
存: 3
存: 4
存: 5
存: 6
存: 7
存: 8
存: 9
存: 10
存: 11
存: 12
存: 13
存: 14
存: 15
存: 16
存: 17
存: 18
存: 19
存: 20
存: 21
存: 22
存: 23
存: 24
存: 25
存: 26
存: 27
存: 28
存: 29
存: 30
存: 31
存: 32
存: 33
存: 34
存: 35
存: 36
存: 37
存: 38
存: 39
存: 40
存: 41
存: 42
存: 43
存: 44
存: 45
存: 46
存: 47
存: 48
存: 49
存: 50
存: 51
存: 52
存: 53
存: 54
存: 55
存: 56
存: 57
存: 58
存: 59
存: 60
存: 61
存: 62
存: 63
存: 64
存: 65
存: 66
存: 67
存: 68
存: 69
存: 70
存: 71
存: 72
存: 73
存: 74
存: 75
存: 76
存: 77
存: 78
存: 79
存: 80
存: 81
存: 82
存: 83
存: 84
存: 85
存: 86
存: 87
存: 88
存: 89
存: 90
存: 91
存: 92
存: 93
存: 94
存: 95
存: 96
存: 97
存: 98
存: 99
存: 100
存: 101
这里是最终的钱数: 101
结果
from multiprocessing import Process, Lock
import time


def check(i):  # 查看不需要上锁
    with open('余票') as f:
        content = f.read()
    print('第%s个人查到余票还剩%s张' % (i, content))


def buy(i, l):
    l.acquire()  # 拿钥匙,锁门
    with open('余票') as f:
        content = int(f.read())

    if content > 0:
        print('\033[31m 第%s个人买到了票\033[0m' % i)
        content -= 1
    else:
        print('第%s个人没有抢到' % i)

    time.sleep(0.1)  # 买完票后,把余票数量重写写入数据库的时间延迟
    with open('余票', 'w') as f:
        f.write(str(content))
    l.release()


if __name__ == '__main__':
    l = Lock()
    for i in range(10):
        # 10个人查票,建立10个查票进程
        p_check = Process(target=check, args=(i + 1,))
        p_check.start()  # 开启进程

    for i in range(10):
        # 10个人抢票,建立10个抢票进程
        p_buy = Process(target=buy, args=(i + 1, l))
        p_buy.start()
抢票

 

信号量 —— multiprocessing.Semaphore

 

Lock,属于互斥锁,也就是一把钥匙配备一把锁,同时只允许锁住某一个数据。而信号量则是多把钥匙配备多把锁,也就是说同时允许锁住多个数据。

 

 

上述讲的Lock,属于互斥锁,也就是一把钥匙配备一把锁,同时只允许锁住某一个数据。而信号量则是多把钥匙配备多把锁,也就是说同时允许锁住多个数据。

比如在一个粉红发廊,里边有5位服务人员,那么这个发廊最多就同时允许进入5位客人,当又有第6位客人来的时候,就需要在门外等待;当服务人员服务完某位客人后,才允许后续的人再进来一个,换句话说,这个发廊最多同时接待5位客人,多的客人必须等待。

信号量同步基于内部计数器,用户初始化一个计数器初值(比如上述例子中就初始化为5),每调用一次acquire(),计数器减1;每调用一次release(),计数器加1。当计数器为0时,acquire()调用被阻塞。这是迪科斯彻(Dijkstra)信号量概念P()和V()的Python实现。信号量同步机制适用于访问像服务器这样的有限资源。信号量与进程池的概念很像,但是要区分开,信号量涉及到加锁的概念

信号量 Semaphore
多把钥匙锁住多把锁

 

from multiprocessing import Process, Semaphore
import time
import random


def func(i, sem):
    sem.acquire()  # 获取钥匙
    print('第%s个人进入小黑屋,拿了钥匙锁了门' % i)
    time.sleep(random.randint(3, 5))
    print('第%s个人离开小黑屋,还了钥匙,打开了门' % i)
    sem.release()  # 还钥匙


if __name__ == '__main__':
    sem = Semaphore(4)  # 允许4个进程执行 进入小黑屋
    # 之后其他人必须等待,有人出来 还了钥匙, 才允许后边的程序进入
    for i in range(1,21):
        p = Process(target=func, args=(i, sem))  # 创建20个禽兽
        p.start()
发廊

 

事件 —— multiprocessing.Event

 

事件处理的机制:全局定义了一个“Flag”(event.is_set()),如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。

python中的事件机制,主要用于主进程控制其他进程的执行,事件主要提供了三个方法 set、wait、clear。

    事件处理的机制:全局定义了一个“Flag”(event.is_set()),如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。

clear:将“Flag”设置为False
set:将“Flag”设置为True
is_set:返回全局‘Flag’的bool值

事件 Event
事件 Event
from multiprocessing import Process,Event

e = Event() # 默认为False
e.set()     
e.clear()   
e.wait()    
e.is_set()

# 事件是通过is_set()的bool值,去标识e.wait() 的阻塞状态
# 当is_set()的bool值为False时,e.wait()是阻塞状态
# 当is_set()的bool值为True时,e.wait()是非阻塞状态
# 当使用set()时,是把is_set的bool变为True
# 当使用clear()时,是把is_set的bool变为False

print(e.is_set())# False wait应该是阻塞住
e.set()# 将is_set 的bool值变为True,将wait变为非阻塞
e.wait()
print(e.is_set())
print(123)
e.clear()
print(e.is_set())
e.wait()
print(123)
举例
from multiprocessing import Process,Event
import time
import random

def tra(e):
    '''信号灯函数'''
    # e.set()
    # print('\033[32m 绿灯亮! \033[0m')
    while 1:# 红绿灯得一直亮着,要么是红灯要么是绿灯
        if e.is_set():# True,代表绿灯亮,那么此时代表可以过车
            time.sleep(5)# 所以在这让灯等5秒钟,这段时间让车过
            print('\033[31m 红灯亮! \033[0m')# 绿灯亮了5秒后应该提示到红灯亮
            e.clear()# 把is_set设置为False
        else:
            time.sleep(5)# 此时代表红灯亮了,此时应该红灯亮5秒,在此等5秒
            print('\033[32m 绿灯亮! \033[0m')# 红的亮够5秒后,该绿灯亮了
            e.set()# 将is_set设置为True

def Car(i,e):
    e.wait()# 车等在红绿灯,此时要看是红灯还是绿灯,如果is_set为True就是绿灯,此时可以过车
    print('第%s辆车过去了'%i)

if __name__ == '__main__':
    e = Event()
    triff_light = Process(target=tra,args=(e,))# 信号灯的进程
    triff_light.start()
    for i in range(50):# 描述50辆车的进程
        if i % 3 == 0:
            time.sleep(2)
        car = Process(target=Car,args=(i+1,e,))
        car.start()
信号灯模拟

 

posted @ 2018-08-21 15:34  Niuli'blog  阅读(561)  评论(0编辑  收藏  举报