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]

相关文章
|
3月前
|
并行计算 安全 Java
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
在Python开发中,GIL(全局解释器锁)一直备受关注。本文基于CPython解释器,探讨GIL的技术本质及其对程序性能的影响。GIL确保同一时刻只有一个线程执行代码,以保护内存管理的安全性,但也限制了多线程并行计算的效率。文章分析了GIL的必要性、局限性,并介绍了多进程、异步编程等替代方案。尽管Python 3.13计划移除GIL,但该特性至少要到2028年才会默认禁用,因此理解GIL仍至关重要。
267 16
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
|
2月前
|
数据采集 Java 数据处理
Python实用技巧:轻松驾驭多线程与多进程,加速任务执行
在Python编程中,多线程和多进程是提升程序效率的关键工具。多线程适用于I/O密集型任务,如文件读写、网络请求;多进程则适合CPU密集型任务,如科学计算、图像处理。本文详细介绍这两种并发编程方式的基本用法及应用场景,并通过实例代码展示如何使用threading、multiprocessing模块及线程池、进程池来优化程序性能。结合实际案例,帮助读者掌握并发编程技巧,提高程序执行速度和资源利用率。
69 0
|
5月前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
5月前
|
调度 iOS开发 MacOS
python多进程一文够了!!!
本文介绍了高效编程中的多任务原理及其在Python中的实现。主要内容包括多任务的概念、单核和多核CPU的多任务实现、并发与并行的区别、多任务的实现方式(多进程、多线程、协程等)。详细讲解了进程的概念、使用方法、全局变量在多个子进程中的共享问题、启动大量子进程的方法、进程间通信(队列、字典、列表共享)、生产者消费者模型的实现,以及一个实际案例——抓取斗图网站的图片。通过这些内容,读者可以深入理解多任务编程的原理和实践技巧。
304 1
|
5月前
|
监控 JavaScript 前端开发
python中的线程和进程(一文带你了解)
欢迎来到瑞雨溪的博客,这里是一位热爱JavaScript和Vue的大一学生分享技术心得的地方。如果你从我的文章中有所收获,欢迎关注我,我将持续更新更多优质内容,你的支持是我前进的动力!🎉🎉🎉
69 0
|
24天前
|
Linux 数据库 Perl
【YashanDB 知识库】如何避免 yasdb 进程被 Linux OOM Killer 杀掉
本文来自YashanDB官网,探讨Linux系统中OOM Killer对数据库服务器的影响及解决方法。当内存接近耗尽时,OOM Killer会杀死占用最多内存的进程,这可能导致数据库主进程被误杀。为避免此问题,可采取两种方法:一是在OS层面关闭OOM Killer,通过修改`/etc/sysctl.conf`文件并重启生效;二是豁免数据库进程,由数据库实例用户借助`sudo`权限调整`oom_score_adj`值。这些措施有助于保护数据库进程免受系统内存管理机制的影响。
|
1月前
|
Linux Shell
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
85 5
|
9月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
|
9月前
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
266 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
|
8月前
|
算法 Linux 调度
探索进程调度:Linux内核中的完全公平调度器
【8月更文挑战第2天】在操作系统的心脏——内核中,进程调度算法扮演着至关重要的角色。本文将深入探讨Linux内核中的完全公平调度器(Completely Fair Scheduler, CFS),一个旨在提供公平时间分配给所有进程的调度器。我们将通过代码示例,理解CFS如何管理运行队列、选择下一个运行进程以及如何对实时负载进行响应。文章将揭示CFS的设计哲学,并展示其如何在现代多任务计算环境中实现高效的资源分配。