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]

相关文章
|
9月前
|
人工智能 安全 调度
Python并发编程之线程同步详解
并发编程在Python中至关重要,线程同步确保多线程程序正确运行。本文详解线程同步机制,包括互斥锁、信号量、事件、条件变量和队列,探讨全局解释器锁(GIL)的影响及解决线程同步问题的最佳实践,如避免全局变量、使用线程安全数据结构、精细化锁的使用等。通过示例代码帮助开发者理解并提升多线程程序的性能与可靠性。
283 0
|
9月前
|
数据采集 NoSQL 调度
当生成器遇上异步IO:Python并发编程的十大实战兵法
本文通过十大实战场景,详解Python中生成器与异步IO的高效结合。从协程演进、背压控制到分布式锁、性能剖析,全面展示如何利用asyncio与生成器构建高并发应用,助你掌握非阻塞编程核心技巧,提升I/O密集型程序性能。
337 0
|
9月前
|
监控 编译器 Python
如何利用Python杀进程并保持驻留后台检测
本教程介绍如何使用Python编写进程监控与杀进程脚本,结合psutil库实现后台驻留、定时检测并强制终止指定进程。内容涵盖基础杀进程、多进程处理、自动退出机制、管理员权限启动及图形界面设计,并提供将脚本打包为exe的方法,适用于需持续清理顽固进程的场景。
|
10月前
|
数据采集 搜索推荐 调度
当生成器遇上异步IO:Python并发编程的十大实战兵法
生成器与异步IO是Python并发编程中的两大利器,二者结合可解决诸多复杂问题。本文通过十个真实场景展示其强大功能:从优雅追踪日志文件、API调用流量整形,到实时数据流反压控制、大文件分片处理等,每个场景都体现了生成器按需生成数据与异步IO高效利用I/O的优势。两者配合不仅内存可控、响应及时,还能实现资源隔离与任务独立调度,为高并发系统提供优雅解决方案。这种组合如同乐高积木,虽单个模块简单,但组合后却能构建出复杂高效的系统。
227 0
Python 高级编程与实战:深入理解面向对象与并发编程
本文深入探讨Python的高级特性,涵盖面向对象编程(继承、多态、特殊方法、类与实例属性)、异常处理(try-except、finally)和并发编程(多线程、多进程、异步编程)。通过实战项目如聊天服务器和异步文件下载器,帮助读者掌握这些技术,编写更复杂高效的Python程序。
|
消息中间件 Linux C++
c++ linux通过实现独立进程之间的通信和传递字符串 demo
的进程间通信机制,适用于父子进程之间的数据传输。希望本文能帮助您更好地理解和应用Linux管道,提升开发效率。 在实际开发中,除了管道,还可以根据具体需求选择消息队列、共享内存、套接字等其他进程间通信方
375 16
|
机器学习/深度学习 分布式计算 API
Python 高级编程与实战:深入理解并发编程与分布式系统
在前几篇文章中,我们探讨了 Python 的基础语法、面向对象编程、函数式编程、元编程、性能优化、调试技巧、数据科学、机器学习、Web 开发、API 设计、网络编程和异步IO。本文将深入探讨 Python 在并发编程和分布式系统中的应用,并通过实战项目帮助你掌握这些技术。
|
数据采集 Java 数据处理
Python实用技巧:轻松驾驭多线程与多进程,加速任务执行
在Python编程中,多线程和多进程是提升程序效率的关键工具。多线程适用于I/O密集型任务,如文件读写、网络请求;多进程则适合CPU密集型任务,如科学计算、图像处理。本文详细介绍这两种并发编程方式的基本用法及应用场景,并通过实例代码展示如何使用threading、multiprocessing模块及线程池、进程池来优化程序性能。结合实际案例,帮助读者掌握并发编程技巧,提高程序执行速度和资源利用率。
702 0
|
数据采集 消息中间件 Java
python并发编程:什么是并发编程?python对并发编程有哪些支持?
并发编程能够显著提升程序的效率和响应速度。例如,网络爬虫通过并发下载将耗时从1小时缩短至20分钟;APP页面加载时间从3秒优化到200毫秒。Python支持多线程、多进程、异步I/O和协程等并发编程方式,适用于不同场景。线程通信方式包括共享变量、消息传递和同步机制,如Lock、Queue等。Python的并发编程特性使其在处理大规模数据和高并发访问时表现出色,成为许多领域的首选语言。
263 3
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####

推荐镜像

更多
下一篇
开通oss服务