python多进程并发编程之互斥锁与进程间的通信

简介: python多进程并发编程之互斥锁与进程间的通信

一、互斥锁

1

2

3

多个进程之间的内存空间是隔离的,但是硬盘,数据库,打印终端都是共享的 。因此当多个进程同时修改硬盘中的同一个文件,或者修改数据库中的同一条记录时,就存在资源竞争的问题,容易出错。

 

加锁的目的就是为了保证多个进程修改同一块数据时,同一时间只能有一个修改,即串行的修改,没错,速度是慢了,牺牲了速度而保证了数据安全。

进程互斥锁代码示例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

import os

import json

import time

import random

from multiprocessing import Process,Lock

def buy_ticket(mutex):

    time.sleep(random.randint(1,3))  #模拟延迟

    ticket_info = json.load(open('ticket_info','r',encoding='utf-8'))

    mutex.acquire()

    if ticket_info["ticket_count"] > 0:

        ticket_info["ticket_count"] -= 1

        json.dump(ticket_info,open('ticket_info','w',encoding='utf-8'))

        print("PID %s 买到票了"% os.getpid())

    else:

        print("pid %s 没买到票,票卖完了!" % os.getpid())

    mutex.release()

 

 

if __name__ == '__main__':

    mutex = Lock()

    for in range(10):

        p = Process(target=buy_ticket,args=(mutex,))

        p.start()

执行结果:

1

2

3

4

5

6

7

8

9

10

PID 10632 买到票了

pid 3808 没买到票,票卖完了!

pid 15488 没买到票,票卖完了!

pid 556 没买到票,票卖完了!

pid 10448 没买到票,票卖完了!

pid 11672 没买到票,票卖完了!

pid 8400 没买到票,票卖完了!

pid 5340 没买到票,票卖完了!

pid 11164 没买到票,票卖完了!

pid 9140 没买到票,票卖完了!

通过上面这个例子,我们知道互斥锁可以一个任务中的某个子任务由并行改为串行,而不是将整个任务改成串行。

总结:

多个进程的内存是相互隔离的,但硬盘,数据库等确实共享的。通过互斥锁就可以实现多个进程之间的通信,并且不会造成数据混乱,保证的数据的安全。但互斥锁将并发改为了串行,降低了效率 ,而且需要我们自己加锁,释放锁,容易出现问题。  

二、IPC机制  

进程间通信(IPC,InterProcess Communication)

1

2

3

是指在不同进程之间传播或交换信息。是一组编程接口,让程序员能够协调不同的进程,使之能在一个操作系统里同时运行,并相互传递、交换信息。这使得一个程序能够在同一时间里处理许多用户的要求。因为即使只有一个用户发出要求,也可能导致一个操作系统中多个进程的运行,进程之间必须互相通话。IPC接口就提供了这种可能性。每个IPC方法均有它自己的优点和局限性,一般,对于单个程序而言使用所有的IPC方法是不常见的。

 

IPC的方式通常有管道(PIPE)(包括无名管道和命名管道)、消息队列、信号量、共享内存、套接字(Socket)、Streams、旗语等。其中 Socket和Streams支持不同主机上的两个进程IPC。

进程间通信(IPC)常见分类的特点

1.管道

1

管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。  

特点:

1

2

3

它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。

它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。

它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。

2.FIFO (命名管道)

1

FIFO,也称为命名管道,它是一种文件类型。

特点:

1

2

FIFO可以在无关的进程之间交换数据,与无名管道不同。

FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。

3.消息队列

1

消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。

特点:

1

2

3

消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。

消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。

消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

4.信号量

1

信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。  

特点:

1

2

3

4

信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。

信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。

每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。

支持信号量组。

5.共享内存

1

共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。  

特点:

1

2

3

共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。

因为多个进程可以同时操作,所以需要进行同步。

信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。  

参考文档:https://blog.csdn.net/python_jw/article/details/79506702  

三、使用队列实现进程间通信  

1.Queue通信机制

首先讲解 一下Queue通信方式。Queue是多进程安全的队列,可以使用Queue实现多进 程之间的数据传递,

通信原理:在内存中建立队列数据结构模型。多个进程都可以通过队列存入内容,取出内容的顺序和存入的顺序保持一致

有两个方法: Put和Get可以进行Queue操作:

put:

1

Put方法用以插入数据到队列中,它还有两个可选参数:blocked和timeout。如果blocked为True (默认值),并且timeout为正值,该方法会阻塞timeout指定的时间, 直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,但该 Queue已满,会立即抛出Queue.Full异常。(即如果blocked为True时,如果队列满,会有一定的等待时间)

get:  

1

Get方法可以从 队列读取并且删除一个元素 。同样,Get方法有两个可选参数:blocked 和timeout。如果blocked为True (默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False, 分两种情况:如果Queue 有 一个值可用, 则立即返回该值;否则,如果队列为空, 则立即抛出 Queue.Empty异常。(形式上与Put相同,不同的仅仅是写入和获取的区别)

2.代码示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

from multiprocessing import Process,Lock,Queue

 

 

def put(queue):

    for in range(5):

        queue.put(i)

 

def get(queue):

    for in range(5):

        print("获取了 %s" % queue.get())

 

 

if __name__ == '__main__':

    queue = Queue(5)

    p1 = Process(target=put,args=(queue,))

    p2 = Process(target=get,args=(queue,))

 

    p1.start()

    p2.start()

四、生产者消费者模型介绍

什么是生产者和消费者模式

1

2

3

4

5

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,

 

消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

 

这个阻塞队列就是用来给生产者和消费者解耦的

为什么要使用生产者消费者模型

1

2

3

生产者指的是生产数据的任务,消费者指的是处理数据的任务,在并发编程中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。

 

同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

代码示例:

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

from multiprocessing import Process, Queue

import time

 

 

def producer(q, name, food):

    count = 0

    while True:

        count +=1

        res = '%s,%s' % (food, count)

        time.sleep(1)  # 生产food得有个过程,就先让睡一会

        print('生产者[%s] 生产了 [%s]' % (name, res))

        q.put(res)

 

 

def consumer(q, name):

    while True:

        res = q.get()

        if res is None: break

        time.sleep(1)

        print('消费者[%s]吃了[%s]' % (name, res))

 

 

if __name__ == '__main__':

    # 容器

    q = Queue()

    p = Process(target=producer, args=(q, 'egon''包子'))

    c = Process(target=consumer, args=(q, 'alex',))

 

    p.start()

    c.start()

运行结果:

1

2

3

4

5

6

生产者[egon] 生产了 [包子,0]

生产者[egon] 生产了 [包子,1]

消费者[alex]吃了[包子,0]

生产者[egon] 生产了 [包子,2]

消费者[alex]吃了[包子,1]

消费者[alex]吃了[包子,2]

相关文章
|
2天前
|
消息中间件 安全 Kafka
Python IPC机制全攻略:让进程间通信变得像呼吸一样自然
【9月更文挑战第12天】在编程领域,进程间通信(IPC)是连接独立执行单元的关键技术。Python凭借简洁的语法和丰富的库支持,提供了多种IPC方案。本文将对比探讨Python的IPC机制,包括管道与消息队列、套接字与共享内存。管道适用于简单场景,而消息队列更灵活,适合高并发环境。套接字广泛用于网络通信,共享内存则在本地高效传输数据。通过示例代码展示`multiprocessing.Queue`的使用,帮助读者理解IPC的实际应用。希望本文能让你更熟练地选择和运用IPC机制。
21 10
|
1天前
|
Python
惊!Python进程间通信IPC,让你的程序秒变社交达人,信息畅通无阻
【9月更文挑战第13天】在编程的世界中,进程间通信(IPC)如同一场精彩的社交舞会,每个进程通过优雅的IPC机制交换信息,协同工作。本文将带你探索Python中的IPC奥秘,了解它是如何让程序实现无缝信息交流的。IPC如同隐形桥梁,连接各进程,使其跨越边界自由沟通。Python提供了多种IPC机制,如管道、队列、共享内存及套接字,适用于不同场景。通过一个简单的队列示例,我们将展示如何使用`multiprocessing.Queue`实现进程间通信,使程序如同社交达人般高效互动。掌握IPC,让你的程序在编程舞台上大放异彩。
8 3
|
3天前
|
安全 开发者 Python
Python IPC大揭秘:解锁进程间通信新姿势,让你的应用无界连接
【9月更文挑战第11天】在编程世界中,进程间通信(IPC)如同一座无形的桥梁,连接不同进程的信息孤岛,使应用无界而广阔。Python凭借其丰富的IPC机制,让开发者轻松实现进程间的无缝交流。本文将揭开Python IPC的神秘面纱,介绍几种关键的IPC技术:管道提供简单的单向数据传输,适合父子进程间通信;队列则是线程和进程安全的数据共享结构,支持多进程访问;共享内存允许快速读写大量数据,需配合锁机制确保一致性;套接字则能实现跨网络的通信,构建分布式系统。掌握这些技术,你的应用将不再受限于单个进程,实现更强大的功能。
19 5
|
3天前
|
消息中间件 Kafka 数据安全/隐私保护
Python IPC实战指南:构建高效稳定的进程间通信桥梁
【9月更文挑战第11天】在软件开发中,随着应用复杂度的提升,进程间通信(IPC)成为构建高效系统的关键。本文通过一个分布式日志处理系统的案例,介绍如何使用Python和套接字实现可靠的IPC。案例涉及定义通信协议、实现日志发送与接收,并提供示例代码。通过本教程,你将学会构建高效的IPC桥梁,并了解如何根据需求选择合适的IPC机制,确保系统的稳定性和安全性。
18 5
|
3月前
|
监控 Linux 应用服务中间件
探索Linux中的`ps`命令:进程监控与分析的利器
探索Linux中的`ps`命令:进程监控与分析的利器
|
2月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
|
2月前
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
61 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
|
1月前
|
算法 Linux 调度
探索进程调度:Linux内核中的完全公平调度器
【8月更文挑战第2天】在操作系统的心脏——内核中,进程调度算法扮演着至关重要的角色。本文将深入探讨Linux内核中的完全公平调度器(Completely Fair Scheduler, CFS),一个旨在提供公平时间分配给所有进程的调度器。我们将通过代码示例,理解CFS如何管理运行队列、选择下一个运行进程以及如何对实时负载进行响应。文章将揭示CFS的设计哲学,并展示其如何在现代多任务计算环境中实现高效的资源分配。
|
2月前
|
存储 缓存 安全
【Linux】冯诺依曼体系结构与操作系统及其进程
【Linux】冯诺依曼体系结构与操作系统及其进程
138 1
|
2月前
|
小程序 Linux
【编程小实验】利用Linux fork()与文件I/O:父进程与子进程协同实现高效cp命令(前半文件与后半文件并行复制)
这个小程序是在文件IO的基础上去结合父子进程的一个使用,利用父子进程相互独立的特点实现对数据不同的操作