上次写了Python进程的基本使用,这一篇,我们讲一下进程的同步锁
一共就讲两个
我来模拟一个买火车票的程序,写一个类模拟乘客
先来做火车票柜台,用一个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其实在工作场景上其实是有区别的,但是使用方法几乎一样,这一篇也只是简单说明,之后还会有一些进程分析,以后再写啦。