Python Process 之进程锁和信号量

上次写了Python进程的基本使用,这一篇,我们讲一下进程的同步锁
一共就讲两个

  • Lock
  • Semaphore 信号量

我来模拟一个买火车票的程序,写一个类模拟乘客
先来做火车票柜台,用一个ini文档模拟一下


文件名: Tickets.ini
下面是内容
[TICKETS]
count = 3
price = 100


我调用的是configparser模块,这个很简单,大家可以查一下
这是用来初始化这个文件的函数

def init_config_file():
    config = configparser.ConfigParser()
    config['TICKETS'] = {'Count': 3,
                         'Price': 100}
    with open('Tickets.ini', 'w') as fd:
        config.write(fd)

然后我会创建一个Passenger类

from multiprocessing import Process
from multiprocessing import Lock
import configparser
import time

class Passenger(Process):
    def __init__(self, name, lock):
        super().__init__()
        self.name = name
        self.lock = lock

    def buy_ticket(self):
        # self.lock.acquire()
        config = configparser.ConfigParser()
        config.read('Tickets.ini') # 读取Tickets.ini文件
        time.sleep(1) # 假装延迟
        # 获取车票数量
        ticket_count = int(config.get('TICKETS', 'count')) 
        # 大于一张才能买
        if ticket_count >= 1:
            print('{0} get a ticket and there also have {1} tickets'.format(self.name, ticket_count - 1))
            # 买完票之后还得把文件内TICKETS数量减一
            config.set('TICKETS', 'count', str(ticket_count - 1))
            time.sleep(1) # 模拟写入延迟
            with open('Tickets.ini', 'w') as fd:
                config.write(fd)
        else:
            print('{} doesn\'t get a ticket'.format(self.name))
        # self.lock.release()

    def run(self):
        self.buy_ticket()

上面的lock请先忽略,下面是执行代码

if __name__ == '__main__':
    init_config_file()
    lock = Lock() # 忽略他
    name_list = ['tom', 'jack', 'allen', 'mike', 'ada'] # 五个人
    process_list = [Passenger(x, lock) for x in name_list]
    for p in process_list:
        p.start()

讲道理,只能有3个人买到票,OK,看结果

mike get a ticket and there also have 2 tickets
tom get a ticket and there also have 2 tickets
jack get a ticket and there also have 2 tickets
allen get a ticket and there also have 2 tickets
ada get a ticket and there also have 2 tickets

居然每个人都买到了票,为什么?
这就是一个同步程序必定会遇到的问题,需要共同操作同一个文件或者内存。
上面这个例子我来解释一下,当第一个人获得TICKETS数据后,还没来得及写入,其实第二个人也拿到了TICKETS数据,那这两个人看到的TICKETS数量,就都是3,第三个人也是,第四个也是,第五个也是。(我这里用sleep来模拟延迟,保证cpu来切换进程,读写io,睡眠,阻塞,都会让cpu来切换)
而且每个人看到的都是3张TICKETS,然后减去一张就是2张,和结果是一样的。

我们需要保证同一时间只能有一个人占据买票柜台,并且不允许别人在我买票的过程中来插队,就算我不买了,也要等我离开才能来买票

  这时候,Lock的作用就来了,他就能占据这个柜台。
from multiprocessing import Process
from multiprocessing import Lock
import configparser
import time

class Passenger(Process):
    def __init__(self, name, lock):
        super().__init__()
        self.name = name
        self.lock = lock

    def buy_ticket(self):
        self.lock.acquire()  # 加锁
        config = configparser.ConfigParser()
        config.read('Tickets.ini')
        time.sleep(1)
        ticket_count = int(config.get('TICKETS', 'count'))
        if ticket_count >= 1:
            print('{0} get a ticket and there also have {1} tickets'.format(self.name, ticket_count - 1))
            config.set('TICKETS', 'count', str(ticket_count - 1))
            time.sleep(1)
            with open('Tickets.ini', 'w') as fd:
                config.write(fd)
        else:
            print('{} doesn\'t get a ticket'.format(self.name))
        self.lock.release()  # 解锁

    def run(self):
        self.buy_ticket()
  • lock.acquire() # 加锁

  • lock.release() # 解锁

    这里要提一点,python 在lock这一块是有做简化的,我们知道父进程和子进程是互相独立的,并且当子进程开始时,会复制父进程之前的数据,全局变量也会复制。
    (这里其实牵涉到共享内存,我就不一一展开,这里大家只要知道,我把锁传进类之后,就是同一把锁,每个进程用的都是同一把锁,
    不是同一个锁,就锁不住了。。。)
    那我在主进程创建的锁,通过传参,传给了各个进程。
    

再看下我的调用

if __name__ == '__main__':
    init_config_file()
    lock = Lock()
    name_list = ['tom', 'jack', 'allen', 'mike', 'ada']
    process_list = [Passenger(x, lock) for x in name_list]
    for p in process_list:
        p.start()

结果

mike get a ticket and there also have 2 tickets
ada get a ticket and there also have 1 tickets
jack get a ticket and there also have 0 tickets
tom doesn't get a ticket
allen doesn't get a ticket

每次从进程被唤醒,到买票结束都会被锁住,只有等到买完票,并且更新完Tickets.ini之后才会解锁。
对于需要多进程同时只允许一个进程进行操作的都能用Lock


第二个内容:Semaphore
还是刚才那个例子,但是我不能卖票了,因为又会出现同时有多个进程修改同一文件的情况,我们就改成占领购票窗口,这次窗口有3个,客户有10个人,那每次都只能三个人占领窗口

semaphore = Semaphore(3) # 3 就代表有三个锁

完整代码

from multiprocessing import Process
from multiprocessing import Semaphore

class Passenger(Process):
    def __init__(self, name, semaphore):
        super().__init__()
        self.name = name
        self.semaphore = semaphore

    def buy_ticket(self):
        self.semaphore.acquire()  # 加锁
        time.sleep(1)
        print(f'{self.name} gets the window')
        print('-------------------------------')
        self.semaphore.release()  # 解锁

    def run(self):
        self.buy_ticket()


if __name__ == '__main__':
    semaphore = Semaphore(3)
    name_list = ['tom', 'jack', 'allen', 'mike', 'ada',
                 'xiaoA', 'xiaoB', 'xiaoC', 'xiaoD', 'xiaoE']
    process_list = [Passenger(x, semaphore) for x in name_list]
    for p in process_list:
        p.start()

结果如果是静态输出其实是看不出的,大家可以用这个代码试一下,程序运行的时候是能看到三个一跳,三个一跳的。


总结:Semaphore和Lock其实在工作场景上其实是有区别的,但是使用方法几乎一样,这一篇也只是简单说明,之后还会有一些进程分析,以后再写啦。

你可能感兴趣的:(Python,3.6)