首页 > 系统相关 >进程间同步(互斥锁)

进程间同步(互斥锁)

时间:2024-05-28 16:56:26浏览次数:27  
标签:fp __ 同步 name 互斥 加锁 进程 ticket 买票

【一】什么是互斥锁

  • 互斥锁是一种用于多线程编程中控制对共享资源访问的机制

  • 限制当前时间段只能由当前进程使用,当前进程使用完成后才能其他进程继续使用

  • 基本原理是在对共享资源进行访问前加锁,使得其他线程无法访问该资源,当访问完成后再解锁,使得其他线程可以进行访问

【二】多个进程共享同一打印终端

  • 进程间的数据不共享,要想共享一套文件系统,就会竞争,从而带来错乱,通过加锁来处理
  • 未加锁
import multiprocessing
import os
import time


def work():
    print(f"{os.getpid()} is starting")
    time.sleep(3)
    print(f"{os.getpid()} is ending")


def main():
    for i in range(5):
        p = multiprocessing.Process(target=work)
        p.start()


if __name__ == '__main__':
    main()

# 打印结果出现混乱
'''
74832 is starting
73188 is starting
67936 is starting
75732 is starting
68348 is starting
74832 is ending
73188 is ending
75732 is ending67936 is ending

68348 is ending
'''
  • 加锁
import multiprocessing
from multiprocessing import Lock
import os
import time

# 为了解决多个子进程共享终端打印导致粘连,于是加锁,为每一个子进程加锁
def work(lock):
    # 加锁,限制只能一个进程使用
    lock.acquire()
    print(f"{os.getpid()} is starting")
    time.sleep(3)
    print(f"{os.getpid()} is ending")
    # 释放锁,将唯一限制解除,其他进程可进行操作
    lock.release()


# 只有当前进程释放锁才能被下一个进程使用
# 加上互斥锁的效果就变成了 ---> 并行 ---> 串行
# 好处就是每一个进程之间的数据是唯一的
def main():
    # 创建锁对象
    lock = Lock()
    # 子进程的列表
    process_list = []
    for i in range(5):
        p = multiprocessing.Process(target=work, args=(lock,))
        process_list.append(p)
        p.start()
    # 最后,我们使用另一个循环来等待所有的进程完成。
    # 在每次迭代中,我们都检查是否有任何尚未完成的进程。
    # 如果有,我们就调用其 join() 方法,等待它完成。
    # 只有当所有的进程都已经完成之后,程序才会退出
    for process in process_list:
        process.join()


if __name__ == '__main__':
    main()

'''
19464 is starting
19464 is ending
75068 is starting
75068 is ending
75108 is starting
75108 is ending
74596 is starting
74596 is ending
75204 is starting
75204 is ending
'''

【三】多个进程共享同一文件

  • 文件当数据库,模拟抢票

  • 未加锁

import json
import multiprocessing
import random
import time


# 初始化数据
def init_ticket():
    with open('ticket.json', 'w', encoding="utf8") as fp:
        json.dump(fp=fp, obj={'ticket_number': 2})


# 读取票的数据
def read_ticket():
    with open('ticket.json', 'r', encoding="utf8") as fp:
        data = json.load(fp=fp)
    return data


# 保存票的数据
def save_ticket(data):
    with open('ticket.json', 'w', encoding="utf8") as fp:
        json.dump(fp=fp, obj=data)


# 查看当前票的数量
def search_ticket(name):
    ticket_number = read_ticket().get('ticket_number')
    print(f"用户:{name}正在查票,当前余票:{ticket_number}")


# 买票
def buy_ticket(name):
    ticket_data = read_ticket()
    time.sleep(random.randint(1, 5))
    # 判断当前是否有票
    if ticket_data.get('ticket_number') > 0:
        # 有票再买票,在原来的数据上 -1
        ticket_data['ticket_number'] -= 1
        # 将买完票的数据更新进原本的数据
        save_ticket(ticket_data)
        print(f"用户:{name} 买票成功!")
    else:
        print(f"用户:{name} 买票失败!票已经售罄!")

# 整合成一个人的功能
def main(name):
    # 所有人都去查票
    search_ticket(name)
    # 所有人都能随便买票
    buy_ticket(name)


if __name__ == '__main__':
    # 初始化票数
    init_ticket()
    process_list = []
    # 多个人买票
    for i in range(1, 5):
        p = multiprocessing.Process(target=main, args=(i,))
        process_list.append(p)
        p.start()
    for process in process_list:
        process.join()
# 可以看到余票数量并未减少,所有人都能查票成功,所有人都能购票成功
'''
用户:1正在查票,当前余票:2
用户:2正在查票,当前余票:2
用户:3正在查票,当前余票:2
用户:4正在查票,当前余票:2
用户:4 买票成功!
用户:2 买票成功!
用户:1 买票成功!
用户:3 买票成功!
'''
  • 加锁
  • 将并发变成串行牺牲效率,但是保证了数据的安全
import json
import multiprocessing
import random
from multiprocessing import Lock
import time


# 初始化数据
def init_ticket():
    with open('ticket.json', 'w', encoding="utf8") as fp:
        json.dump(fp=fp, obj={'ticket_number': 2})


# 读取票的数据
def read_ticket():
    with open('ticket.json', 'r', encoding="utf8") as fp:
        data = json.load(fp=fp)
    return data


# 保存票的数据
def save_ticket(data):
    with open('ticket.json', 'w', encoding="utf8") as fp:
        json.dump(fp=fp, obj=data)


# 查看当前票的数量
def search_ticket(name):
    ticket_number = read_ticket().get('ticket_number')
    print(f"用户:{name}正在查票,当前余票:{ticket_number}")


# 买票
def buy_ticket(name):
    ticket_data = read_ticket()
    time.sleep(random.randint(1, 5))
    # 判断当前是否有票
    if ticket_data.get('ticket_number') > 0:
        # 有票再买票,在原来的数据上 -1
        ticket_data['ticket_number'] -= 1
        # 将买完票的数据更新进原本的数据
        save_ticket(ticket_data)
        print(f"用户:{name} 买票成功!")
    else:
        print(f"用户:{name} 买票失败!票已经售罄!")

# 整合成一个人的功能
def main(name,lock):
    # 所有人都去查票
    search_ticket(name)
    # 所有人都能随便买票
    # 再买票环节加锁
    lock.acquire()
    buy_ticket(name)
    # 释放锁
    lock.release()


if __name__ == '__main__':
    # 在主进程中生成一把锁,让所有的子进程去抢,谁抢到谁就先买票
    lock = Lock()
    # 初始化票数
    init_ticket()
    process_list = []
    # 多个人买票
    for i in range(1, 5):
        p = multiprocessing.Process(target=main, args=(i,lock))
        process_list.append(p)
        p.start()
    for process in process_list:
        process.join()

# 所有人都能查票成功,但是只有前两名购票成功
'''
用户:1正在查票,当前余票:2
用户:2正在查票,当前余票:2
用户:3正在查票,当前余票:2
用户:4正在查票,当前余票:2
用户:1 买票成功!
用户:2 买票成功!
用户:3 买票失败!票已经售罄!
用户:4 买票失败!票已经售罄!
'''

【四】互斥锁的优缺点

【1】加锁的优点

  • 加锁可以保证多个进程修改同一块数据时,数据不错乱

    • 同一时间只能有一个任务可以进行修改,即串行的修改
    • 牺牲了速度却保证了数据安全。

【2】加锁的缺点

  • 虽然可以用文件共享数据实现进程间通信,但问题是:

    • 效率低(共享数据基于文件,而文件是硬盘上的数据),需要自己加锁处理,容易发生死锁现象

【3】优化方案

  • 使用基于消息的IPC通信机制:队列和管道

    • 效率高(多个进程共享一块内存的数据)
  • 我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。

【五】特别提醒

  • 锁不要轻易使用,容易造成死锁现象
  • 锁只在处理数据的部分加,用来保证数据的安全(只在争抢数据的环节加锁)

标签:fp,__,同步,name,互斥,加锁,进程,ticket,买票
From: https://www.cnblogs.com/ligo6/p/18218358

相关文章

  • 进程间通信(队列和生产消费模型)
    【一】引入【1】什么是进程间通信进程间通信(Inter-ProcessCommunication,IPC)是指两个或多个进程之间进行信息交换的过程【2】如何实现进程间通信借助于消息队列,进程可以将消息放入队列中,然后由另一个进程从队列中取出这种通信方式是非阻塞的,即发送进程不需要等待接收进......
  • 多进程理论
    【一】什么是进程【1】进程概念正在进行的一个过程或者说一个任务而负责执行任务则是cpu进程其实就是一个正在运行的程序【2】单任务单核+多道,实现多个进程的并发执行同一时刻只能做一个任务(cpu同一时间只能干一个活)【3】多任务同一时刻可以做多个任务【二】......
  • 【go从入门到精通】精通并发编程-使用atomic管理状态和同步的无锁技术
    了解原子计数器        在Go中,原子计数器是多个goroutine可以同时访问的共享变量。术语“原子”是指在计数器上执行的操作的不可分割的性质。在Go中,原子计数器允许多个goroutine安全地更改共享变量,而无需使用锁或任何其他显式同步,这可确保数据完整性并避免竞......
  • 简单理解Zookeeper之数据同步机制
    写入数据流程请求发给Leaderclient向Zookeeper集群的Leader节点发送写请求Leader节点接收到写请求后,会对请求进行预处理,并为这次写操作分配一个全局唯一的递增ID(ZXID)。Leader将这个写请求(提案)广播给所有的Follower节点。这个提案包含了请求的具体内容和分配的ZXID。每个......
  • SD8906A恒定批量降压转换器集成电路IC同步整流器SOT封装
    该SD8906A是一个恒定频率,电流模式PWM降压转换器。该器件集成了一个主开关和一个同步整流器,无需外部肖特基二极管即可实现高效率。它是为单节锂离子(Li+)电池供电的便携式设备的理想选择。输出电压可调低至0.6V。该SD8906A还可以运行在100%的低压差操作占空比,延长便携式系统......
  • CDC 数据实时同步入湖的技术、架构和方案(截至2024年5月的现状调研)
    博主历时三年精心创作的《大数据平台架构与原型实现:数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行,点击《重磅推荐:建大数据平台太难了!给我发个工程原型吧!》了解图书详情,京东购书链接:https://item.jd.com/12677623.html,扫描左侧二维码进入京东手......
  • 进程A与B互传信号并做出反应
    进程A与B互传信号并做出反应练习:要求进程A创建一条消息队列之后向进程B发送SIGUSR1信号,进程B收到该信号之后打开消息队列并把进程的PID作为消息写入到消息队列中,要求进程B在写入消息之后,发SIGUSR2信号给进程A,进程A收到该信号则从消息队列中读取消息并输出消息正文的内容......
  • 系统编程练习题----使用消息队列实现两个进程之间的通信
    目录题目思路代码展示进程A进程B结果展示题目要求进程A创建一条消息队列之后向进程B发送SIGUSR1信号,进程B收到该信号之后打开消息队列并写入一段信息作为消息写入到消息队列中,要求进程B在写入消息之后,发SIGUSR2信号给进程A,进程A收到该信号则从消息队列中读取消息并输出消息正文......
  • 【Linux学习】进程间通信 (2) —— 信号
    下面是有关进程通信中信号的相关介绍,希望对你有所帮助!小海编程心语录-CSDN博客目录1.信号 1.1概念 1.2信号的产生 1.3信号的处理方式 2.函数 2.1kill()函数 2.2 signal()函数 2.3 sigaction()函数 2.4 sigprocmask()函数 2.5sigqueue()函数......
  • 《TCP/IP网络编程》(第十章)多进程服务器端(2)
    基于进程的并发服务器我们将扩展之前的回声服务器,使其可以同时向多个客户端体提供服务,实现模型如下图所示即每当有客户端向服务器请求服务时,服务器端都创建一个子进程为其提供服务,比如有5个客户端请求服务,则创建个5子进程。通过fork()复制的文件描述符下图是父进程调用......