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]

相关文章
|
5天前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
13天前
|
存储 Unix Linux
进程间通信方式-----管道通信
【10月更文挑战第29天】管道通信是一种重要的进程间通信机制,它为进程间的数据传输和同步提供了一种简单有效的方法。通过合理地使用管道通信,可以实现不同进程之间的协作,提高系统的整体性能和效率。
|
13天前
|
消息中间件 存储 供应链
进程间通信方式-----消息队列通信
【10月更文挑战第29天】消息队列通信是一种强大而灵活的进程间通信机制,它通过异步通信、解耦和缓冲等特性,为分布式系统和多进程应用提供了高效的通信方式。在实际应用中,需要根据具体的需求和场景,合理地选择和使用消息队列,以充分发挥其优势,同时注意其可能带来的复杂性和性能开销等问题。
|
17天前
|
调度 iOS开发 MacOS
python多进程一文够了!!!
本文介绍了高效编程中的多任务原理及其在Python中的实现。主要内容包括多任务的概念、单核和多核CPU的多任务实现、并发与并行的区别、多任务的实现方式(多进程、多线程、协程等)。详细讲解了进程的概念、使用方法、全局变量在多个子进程中的共享问题、启动大量子进程的方法、进程间通信(队列、字典、列表共享)、生产者消费者模型的实现,以及一个实际案例——抓取斗图网站的图片。通过这些内容,读者可以深入理解多任务编程的原理和实践技巧。
41 1
|
24天前
|
Python
Python中的多线程与多进程
本文将探讨Python中多线程和多进程的基本概念、使用场景以及实现方式。通过对比分析,我们将了解何时使用多线程或多进程更为合适,并提供一些实用的代码示例来帮助读者更好地理解这两种并发编程技术。
|
1月前
|
数据挖掘 程序员 调度
探索Python的并发编程:线程与进程的实战应用
【10月更文挑战第4天】 本文深入探讨了Python中实现并发编程的两种主要方式——线程和进程,通过对比分析它们的特点、适用场景以及在实际编程中的应用,为读者提供清晰的指导。同时,文章还介绍了一些高级并发模型如协程,并给出了性能优化的建议。
30 3
|
1月前
|
存储 Python
Python中的多进程通信实践指南
Python中的多进程通信实践指南
19 0
|
6月前
|
安全 Java 数据处理
Python网络编程基础(Socket编程)多线程/多进程服务器编程
【4月更文挑战第11天】在网络编程中,随着客户端数量的增加,服务器的处理能力成为了一个重要的考量因素。为了处理多个客户端的并发请求,我们通常需要采用多线程或多进程的方式。在本章中,我们将探讨多线程/多进程服务器编程的概念,并通过一个多线程服务器的示例来演示其实现。
|
2月前
|
调度 Python
python3多进程实战(python3经典编程案例)
该文章提供了Python3中使用多进程的实战案例,展示了如何通过Python的标准库`multiprocessing`来创建和管理进程,以实现并发任务的执行。
87 0
|
3月前
|
Python
python Process 多进程编程
python Process 多进程编程
39 1