进程锁(互斥锁)
进程锁(互斥锁)
一、什么是进程同步(互斥锁,进程锁)
- 互斥锁(Mutex)是一种用于多线程编程中控制对共享资源访问的机制。
- 其作用是保证在同一时刻只有一个线程在访问共享资源,从而避免多个线程同时读写数据造成的问题。
- 互斥锁的基本原理是在对共享资源进行访问前加锁,使得其他线程无法访问该资源,当访问完成后再解锁,使得其他线程可以进行访问。
- 通过这种方式,可以保证同一时间只有一个线程在执行关键代码段,从而保证了数据的安全性。
- 需要注意的是,互斥锁会带来一些额外的开销
二、具体场景
- 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,
- 而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理
[1]未加锁
- 并发运行,效率高,但竞争同一打印终端,带来了打印错乱并发运行,效率高,但竞争同一打印终端,带来了打印错乱
from multiprocessing import Process
import time
import os
def run(number,pid):
print(f'进程{number} : {pid} 开始!')
time.sleep(2)
print(f'进程{number} : {pid} 结束!')
if __name__ == '__main__':
for i in range(1, 5):
p = Process(target=run, args=(i, os.getpid()))
p.start()
# 因为并发运行都竞争同一打印终端,所以带来了打印错乱
"""
进程1 : 37028 开始!
进程2 : 37028 开始!
进程3 : 37028 开始!
进程4 : 37028 开始!
进程1 : 37028 结束!
进程2 : 37028 结束!
进程4 : 37028 结束!进程3 : 37028 结束!
"""
[2]加锁
- 由并发变成了串行,牺牲了运行效率,但避免了竞争
from multiprocessing import Process, Lock
import time
import os
def run(number, lock):
lock.acquire()
print(f'进程{number} : {os.getpid()} 开始!')
time.sleep(2)
print(f'进程{number} : {os.getpid()} 结束!')
lock.release()
if __name__ == '__main__':
lock = Lock()
for i in range(1, 5):
p = Process(target=run, args=(i, lock))
p.start()
"""
进程1 : 50592 开始!
进程1 : 50592 结束!
进程2 : 28368 开始!
进程2 : 28368 结束!
进程3 : 22644 开始!
进程3 : 22644 结束!
进程4 : 33264 开始!
进程4 : 33264 结束!
"""
三、小练习
多个人同时开始买票进程
- 未加锁
from multiprocessing import Process
import json
import time
import os
# 初始化票的数据
def init_data():
with open(f'{os.path.join(os.path.dirname(__file__), "data.json")}', 'w') as fp:
json.dump({'num': 2}, fp)
# 取出票数的数据
def read_data():
with open(f'{os.path.join(os.path.dirname(__file__), "data.json")}', 'r') as fp:
data = json.load(fp)
return data
# 储存数据
def save_data(data):
with open(f'{os.path.join(os.path.dirname(__file__), "data.json")}', 'w') as fp:
json.dump(data, fp)
def search_ticket(num):
data = read_data()
print(f'用户{num}正在查票!余票{data["num"]}')
def buy_ticket(num):
data = read_data()
time.sleep(1)
if data['num'] == 0:
print('票已售罄!')
else:
data['num'] -= 1
print(f'用户{num}买票成功!')
save_data(data)
def main(num):
search_ticket(num)
buy_ticket(num)
if __name__ == '__main__':
init_data()
p_list = []
for i in range(5):
p = Process(target=main, args=(i + 1,))
p.start()
p_list.append(p)
for p in p_list:
p.join()
"""
用户1正在查票!余票2
用户1买票成功!
用户2正在查票!余票2
用户2买票成功!
用户3正在查票!余票2
用户3买票成功!
用户5正在查票!余票2
用户5买票成功!
用户4正在查票!余票2
用户4买票成功
"""
- 加锁
from multiprocessing import Process, Lock
import json
import time
import os
# 初始化票的数据
def init_data():
with open(f'{os.path.join(os.path.dirname(__file__), "data.json")}', 'w') as fp:
json.dump({'num': 2}, fp)
# 取出票数的数据
def read_data():
with open(f'{os.path.join(os.path.dirname(__file__), "data.json")}', 'r') as fp:
data = json.load(fp)
return data
# 储存数据
def save_data(data):
with open(f'{os.path.join(os.path.dirname(__file__), "data.json")}', 'w') as fp:
json.dump(data, fp)
def search_ticket(num):
data = read_data()
print(f'用户{num}正在查票!余票{data["num"]}')
def buy_ticket(num):
data = read_data()
time.sleep(1)
if data['num'] == 0:
print('票已售罄!')
else:
data['num'] -= 1
save_data(data)
print(f'用户{num}买票成功!')
def main(num, lock):
search_ticket(num)
lock.acquire()
buy_ticket(num)
lock.release()
if __name__ == '__main__':
init_data()
p_list = []
lock = Lock()
for i in range(5):
p = Process(target=main, args=(i + 1, lock))
p.start()
p_list.append(p)
for p in p_list:
p.join()
"""
用户1正在查票!余票2
用户3正在查票!余票2
用户2正在查票!余票2
用户4正在查票!余票2
用户5正在查票!余票2
用户1买票成功!
用户3买票成功!
票已售罄!
票已售罄!
票已售罄
"""
- 特殊方法(每次都读取数据)
from multiprocessing import Process
import json
import time
import os
# 初始化票的数据
def init_data():
with open(f'{os.path.join(os.path.dirname(__file__), "data.json")}', 'w') as fp:
json.dump({'num': 2}, fp)
# 取出票数的数据
def read_data():
with open(f'{os.path.join(os.path.dirname(__file__), "data.json")}', 'r') as fp:
data = json.load(fp)
return data
# 储存数据
def save_data(data):
with open(f'{os.path.join(os.path.dirname(__file__), "data.json")}', 'w') as fp:
json.dump(data, fp)
def main(i):
data = read_data()
print(f'当前票数字典{data}')
if data.get('num') == 0:
print('票已售罄!')
else:
data['num'] -= 1
save_data(data)
print(f'用户{i}买票成功!')
if __name__ == '__main__':
init_data()
lock = Lock()
for i in range(5):
p = Process(target=main, args=(i + 1,))
p.start()
"""
当前票数字典{'num': 2}
用户1买票成功!
当前票数字典{'num': 1}
用户2买票成功!
当前票数字典{'num': 0}
票已售罄!
当前票数字典{'num': 0}
票已售罄!
当前票数字典{'num': 0}
票已售罄
"""
四、互斥锁的优缺点
[1]加锁的优点
加锁可以保证多个进程修改同一块数据时
- 同一时间只能有一个任务可以进行修改,即串行的修改
- 没错,速度是慢了,但牺牲了速度却保证了数据安全
[2]加锁的缺点
- 虽然可以用文件共享数据实现进程间通信,但问题是:
- 1.效率低(共享数据基于文件,而文件是硬盘上的数据)
- 2.需要自己加锁处理
[3]优化方案
- 因此我们最好找寻一种解决方案能够兼顾:
- 1、效率高(多个进程共享一块内存的数据)
- 2、帮我们处理好锁问题。这就是
mutiprocessing
模块为我们提供的基于消息的IPC通信机制:队列和管道。
- 1 队列和管道都是将数据存放于内存中
- 2 队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,
- 我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。
五、行锁与表锁
行锁和表锁是数据库中常用的锁定机制。
[1]行所
- 行锁是在对数据库表中的某个数据行进行修改时
- 一次只允许一个用户操作该行
- 其他用户如果需要修改该行数据就必须等待。
- 通过行锁定可以避免多个用户同时修改同一行数据所导致的数据不一致问题。
[2]表锁
- 表锁则是当一个用户操作某个数据库表时
- 会锁定整个表,其他用户同时不能操作该表。
- 这在一些特殊场景下比如表维护、备份等是非常有用的。
[3]小结
- 总的来说
- 行锁定是比较细粒度的锁定
- 而表锁定则是更为粗粒度的锁定方法。
六、特别提醒
- 锁不要轻易使用,容易造成死锁现象
- 锁只在处理数据的部分加,用来保证数据的安全(只在争抢数据的环节加锁)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具