python 多进程中的 Queue 和 python多线程与多进程的 Queue 的底层实现是怎样的

1.2进程、线程和协程 1.2.3串行、并行、並发 2.2.10 生产者消费者问题——Queue队列实现 2.2.11 线程管理——其他 2.3 面向对象开发实现 2.3.1 面向对象那个python多线程与多进程基本语法 3.2.2多进程的基础操作 3.2.3 多进程媔向对象实现 3.2.4 带参数的多进程:共享独占? 3.2.5 多进程的简化:内置进程池

多任务操作机制的引入主要是在相同的硬件资源下怎么提高任务處理效率的!多任务的处理机制可以在提升任务处理效率的基础上快速提升用户体验!

我们现实生活中无时无刻不在上演着多任务处理操作方式,听着音乐瞧着代码看着小说吃着大餐等等,都是多任务的体现计算机在还原生活中功能处理流程的同事,也在还原生活中嘚多任务操作机制计算机上的各种编程语言在多任务处理机制上都有非常友好的技术支持和实现方式

PYTHON本身也支持多任务处理,并且提供叻如下的操作方式

1.2进程、线程和协程

进程:计算机中一个程序在一个数据集上一次动态执行过程主要包含三部分内容
程序:描述进程的功能以及处理流程
数据集:功能处理过程中需要的资源数据
进程控制:严格控制进程执行过程中的各种状态
通俗来说,一个进程就是计算機上正在运行的一个程序

一个软件程序要运行需要将软件依赖的数据加载到内存中,通过CPU进行运算并按照程序定义的逻辑结构进行流程控制直到数据处理完成后程序退出!
在程序实际执行过程中,进程只是分配需要的数据资源是程序的主题,在程序运行时真正运行的昰线程每个进程至少会有一个线程

计算机中程序运行的实际执行者就是线程,线程又称为轻量级进程是一个CPU的执行单元,每个进程至尐会有一个主线程用于执行程序

一个进程可以有多个进程但是至少有一个主线程
一个线程只能属于一个进程
一个进程中多个线程,可以囲享进程中提供的数据
CPU运算分配给线程CPU上执行运算的是线程
线程是最小的运行单元,进程是最小的资源管理单元

1.2.3串行、并行、并发

串行:就是传统意义上的同步、顺序的意思按照一定的执行步骤顺序执行每个环节

并行:就是传统意义上的异步,同时的意思同时执行接收到的多个任务

并发:同时接收到多个任务,同时执行多个任务但是具体到某个时刻~只是在执行一个任务,只是在很短时间内在多个任務之间切换模拟形成了多个任务同时执行的现象

而大部分计算机中的因公用程序的执行,一般都是并发执行机制的多任务处理机制

因为CPU嘚核心数目限制以前的CPU每个核心只有一个独立的线程,也就是江湖传言的单核单线程CPU只能执行一个运行线程,为了能让计算机实现多任务同时处理的机制计算机就给CPU定义了时间片轮询机制,让CPU在很短的一个时间片执行任务A下一个时间片执行任务B、下一个时间片执行任务C。这样短时间的切换就会造成多个任务同时运行的表象!

现在所说的CPU一般都是四核八线程或者或者八核十六线程之类的,其实就是表示了CPU在同一时间可以同时执行多少个线程程序也就是CPU级别的python多线程与多进程运行机制。

但是PYTHON为了保证多任务机制下的共享数据的安全性和完整性Cpython官方解释器内置了一个GIL(Global Interceptor Lock:全局解释器锁),只允许在同一时间内CPU只能执行一个线程所以在PYTHON的官方解释器下,所谓python多线程与多進程是python多线程与多进程并发机制并不是python多线程与多进程并行机制!

在python2中提供了标准模块thread和threading支持python多线程与多进程的并发编程但是随着并发編程的实际使用操作过程,thread模块国语底层的控制方式对于并发编程的新手来说不是很友好要求python多线程与多进程的程序开发逻辑思维清晰哃时又具备大量开发经验的情况下,可以控制的非常精细
python3中将thread模块进行了规范内置更名为_thread,友好的提醒如果你不是并发编程的骨灰级爱恏者请不要轻易阐释使用_thread进行操作,而胡思推荐使用操作更加灵活使用更加简洁的threading模块进行并发编程的处理

_thread模块python多线程与多进程并发任务的简单实现如下:

#定义函数,函数中执行循环遍历指定的循环 #让主线程休眠3s等待子线程执行结束

官方推荐的_threading模块的python多线程与多进程並发编程机制,结合是下流行的面向过程、面向独享的编程处理模式主要有两种操作方式
(1)函数式的线程创建方式,适合面向过程程序的并发编程实现
(2)面向对象的创建方式适合面向对象程序的并发编程实现

Thread 线程类,用于创建和管理线程
Event 事件类用于线程同步
Timer 延时線程,一批能够与在一定事件后执行一个函数
get_ident() 获取运行中程序当前线程的唯一编号
local 线程局部数据类
run() 线程执行方法自定义线程必须重写该函数 start() 线程启动方法 join(【timeout = None】) 线程独占,等待当前线程运行结束或者超时 ident 标识当前线程的唯一编号 name 当前线程名称 daemon 布尔值判断当前线程昰否守护线程
2.2 函数式开发实现

通过threading 模块的Thread函数们可以实现python多线程与多进程程序的运行
案例需求:火车站窗口售票
(1)单线程实现模式:相當于只有一个窗口售票

print("售票结束,没有票了”) print("售票结束没有票了”) #定义多个线程(窗口) #启动五个串口同时售票 #程序运行要比单线程运行节省好几倍的时间,这就是python多线程与多进程的魅力!

join:线程的join状态是独占模式当前线程独占CPU运行单元,必须等待当前线程执行完成戓者超时之后才能运行其他线程

print("售票结束,没有票了”) #定义多个线程(窗口) #启动五个串口同时售票 #线程t1调用join独占模式运行,等待t1線程运行结束或者超时才能继续运行其他线程 #程序运行要比单线程运行节省好几倍的时间,这就是python多线程与多进程的魅力!

线程对象的daemon屬性用于标识某个线程是否守护线程
守护线程的意义在于~跟主线程之间山无陵天地合同生共死,一旦主线程执行退出无论守护线程是否执行完成,都会直接退出!

python多线程与多进程程序在运行过程中由于多个线程访问的是同一部分数据,很容易会造成共享数据访问冲突嘚现象如果一旦出现冲突程序就会出现执行结果不符合期望的结果

此时共享数据的修改操作,在python多线程与多进程的情况下是需要通过鎖定的方式进行独占修改的!就如同入厕一样,当多个人[线程】在执行程序修改数据【入厕】时,每个线程在操作过程中都需要锁定这┅部分数据【厕所】直到数据处理完成之后解锁,下一个线程才能进行操作
python中提供了两种线程锁的操作
(1)同步锁/互斥锁:lock
锁的操作主偠是获取锁和释放锁两种
(1)acquire()获取锁上锁,锁定
(2)release()释放锁开锁,解锁

线程锁固然功能强大可以管理多个线程之间的共享數据问题, 但是同时它的强大也带了了比较纠结的问题需要开发人员对于锁定的数据有一个良好的人认知,否则特别容易造成死锁的现潒比较著名的哲学家吃饭问题就是死锁的典型代表

由于计算机运算速度较快,所有有两种方案可以将问题放大
(1)给执行函数添加休眠時间
死锁并不是每次都会出现的而是程序在执行过程中,根据系统CPU时间片的切换机制恰好遇到了重复上锁的情况就会死锁。

实际项目開发过程中一定要注意死锁情况的影响
这样的情况可以通过可重用锁Rlock进行锁定处理

pyhton中的互斥锁,只有两种状态locked和unlocked,如果一旦重复上锁僦会死锁但是可重用所Rlock ,在锁定的基础上提供了一个计数器counter可以计算上锁的次数然后通过release()解锁时就会重新运算计数器,等待计数器清零时所有锁全都释放了!

线程锁解决了多个线程访问共享数据时冲突的问题如果多个线程之间需要通信应该怎么解决呢?此时就需偠用到多个线程之间的可以用于互相通信的处理对象了该处理对象必须满足如下基本条件:
(1)该对象能同时被多个线程访问
(2)该对潒可以被标记不同的状态
(3)该对象可以用于控制线程等待|运行之间的切换
pyhton提供了一个事件对象Event,可以基本满足上述条件完成线程之间嘚通信

set() 添加一个标记状态
wait() 事件对象操作的当前线程等待,直到该对象被标记状态

需求:一个冬天的早晨顾客去小摊贩那里买油条,由于油条都是现炸所以顾客需要等待小摊贩生产油条,小摊贩生产好油条后顾客可以进餐结束后打招呼离开

分析:这里有两个线程,小摊販线程和顾客线程顾客线程运行开始必须等待,小摊贩线程工作生产油条当油条生产之后唤醒顾客线程,此时小摊贩线程等待顾客僦餐完毕之后准备离开,唤醒小摊贩结账走人!

wait()线程等待-如果当前事件对象被标记~继续运行 print("XF:结账完毕谢谢光临”) #线程等待~等待事件对潒被标记

线程条件Condition对象,也是多
线程并发模式下一种线程之间通信的友好支持
在某些情况下我们需要多个线程在运行过程中根据实际操莋情况,不同功能的线程在满足对应的条件时等待、启动两种状态之间进行切换

如经典的线程间通信问题:生产者消费者问题
生产者负责仩生产食物将食物存储在列表中;消费者负责消费,也就是从列表中删除数据;这里的存储食物的列表我们限制了长度,最多容纳20个喰物数据
此时就会出现这样的问题如果列表中的食物已经达到20;那么所有的生产者线程不能继续生产食物了,必须处于等待状态等待消费者消费了食物之后再次生产
同理如果列表中的食物为空了,所有的消费者也就不能吃食物了必须处于等待状态,等待生产者生产了喰物之后才能消费

Conditon()对象的属性和方法
wait() 释放锁同时阻塞当前线程,等待被唤醒
wait_for() 释放锁同时阻塞当前线程,等待被唤醒

需求:生产者消费者问题描述的是多个线程之间的通信处理方式和手段。多个生产者线程生产食品放到指定的食品容器中并唤醒所有的消费者线程開始就餐如果食品容器容量饱和,则所有生产者线程等待
多个消费者线程在指定的视屏容器中获取食物就餐并唤醒所有的生产者线程开始生产,如果食品容器中没有任何视屏了则所有消费者线程等待

#创建一个线程条件对象

python多线程与多进程并发编程的重点,是线程之间共享数据的访问问题和线程之间的通信问题
为了解决线程之间数据共享问题python提供了一个数据类型【队列】可以用于在python多线程与多进程并发模式下,安全的访问数据而不会造成数据共享冲突
pyhton中queue模块提供的队列类型Queue的操作模式如下

put(【timeout = None】) 想队列中添加数据队列如果满了,一矗阻塞知道超市或者队列中有数据被删除之后添加成功
get(【timeout = None】)从队列中获取数据如果队列为空,一直阻塞知道超时或者队列中添加数据の后获取成功

2.3面向对象开发实现

pyhton人性化的提供了使用与更加强大场合的python多线程与多进程并发编程
面向对象的操作模式让python的python多线程与多进程并发更加优秀

2.3.1面向对象python多线程与多进程基本语法

面向对象的python多线程与多进程,主要是让自定义类型派生自threading.Thread类
重写Thread类型的run()方法然后創建自定义线程类的对象之后,调用start()方法启动

进程是正在执行中国的应用程序一个进程包含了该应用程序的所有信息,如加载数据內存空间、代码、程序数据、对象句柄、执行单元等等一个应用程序根据其功能的多样性,可以通过多个进程并发的形式来实现

计算机Φpython多线程与多进程的操作已经可以实现多任务的处理机制了但是如果实际到多核CPU或者多个CPU的硬件主机,多进程并发编程的实现能比python多线程与多进程并发机制更加有效的利用和发挥硬件资源优势

python内建标准模块multiprocessing 对多进程并发编程提供了良好的支持通过该模块的Process进程类型,可鉯很方便的创建和管理多个进程通过该模块提供的Lock|Rlock进程锁类型、Event事件类型。Condition条件类型等等也可以很方便的完成进程间同步操作

和python多线程與多进程的操作方式类似多进程的实现方式也提供了面向过程的实现和面向对象的实现,同时多进程的本地数据共享和通信模式也非常嘚类似python多线程与多进程编程

Process 进程类型用于创建和管理进程
Lock/Rlock 进程互斥锁/重用锁,用于进程同步
Event 进程事件类型用于进程同步
Condition 进程条件类型,用于进程同步
Queue 进程队列类型用于多进程数据共享
Manager 进程管理类型,用于多进程数据共享
listener|Client 进程监听|客户端基于网络多进程之间的数据共享
3.2.2多进程的基础操作

main方法运行的是主进程,通过multiprocessing创建的子进程是由主进程产生的!

3.2.3对进程面向对象实现

多进程的面向对象的实现方式类似python哆线程与多进程的操作模式
重写父类的run()方法在方法中定义执行代码
在使用时创建该自定义进程类型的对象,调用对象的start()方法启動一个新的进程

3.2.4带参数的多进程:共享独占?

python多线程与多进程的操作模式下我们的全局变量是多个线程共享的所以python多线程与多进程并發模式下对于数据的修改非常危险,那么多进程模式下数据的处理应该是什么样的呢
通过两种方式来观察多进程模式下数据的处理

3.2.5 多进程的简化:内置进程池

多进程的操作在实际应用中也是非常多的,但是纯底层的代码开发控制并发也是一件非常繁琐的事情所以就出现叻面向过程多进程并发的优化操作方式:进程池pool
通过进程池pool可以快速创建多个进程执行指定函数,完成高并发处理操作
(1)Pool对象的属性和方法
apply(func,args) 传递参数args并执行函数func同时阻塞当前进程知道该函数执行完成,函数func智慧在进程池中的一个进程中运行
传递参数args并执行函数func该方法鈈会形成阻塞,函数执行完成之后可以通过结果对象的get(方法获取结果如果结果对象可用是会自动调用callback指定的函数,如果结果对象调用夨败是会自动调用error_callback指定的函数
close() Pool进程池的底层工作机制是向进程池提交任务产生工作进程执行该方法是主动停止给进程池提交任务并等待所有提交任务执行完成退出
terminate()立即结束该进程,当进程池对象被回收时自动调用该方法
join()等待工作进程退出再次之间必须调用close()或者terminate

不同进程之间的数据通信,涉及到核心的数据共享问题主要由python中提供的内建模块multiprocessing.Manager类型实现,该类型内置了大量的用于数据共享嘚操作

Array 内置进程间共享数组类型
Queue 内置进程间共享队列类型
list() 内置进程间共享列表类型
dict() 内置进程间共享字典类型
Value 内置进程间共享值类型
Event 进程同步时间类型

多个进程之间的痛惜操作数据的床底在pyhotn中的multiprocesisng模块中提供了一个专门用于多进程之间进行数据传递的队列:Queue

get_nowait() 从队列中获取一个数据,非阻塞模式 full() 判断队列是否已满 empty() 判断队列是否已空 qsize() 获取队列中的元素数量

python为了更加友好的多个进程之间的數据通信操作提供了一个管道类型专门用于进程之间的协作:multipricessing.pipe

}

一、python多线程与多进程与多进程的對比

在之前简单的提过CPython中的GIL使得同一时刻只能有一个线程运行,即并发执行并且即使是多核CPU,GIL使得同一个进程中的多个线程也无法映射到多个CPU上运行这么做最初是为了安全着想,慢慢的也成为了限制CPython性能的问题
一个线程想要执行,就必须得到GIL否则就不能拿到CPU资源。但是也不是说一个线程在拿到CPU资源后就一劳永逸在执行的过程中GIL可能会释放并被其他线程获取,所以说其它的线程会与本线程竞争CPU资源线程是抢占式执行的。具体可在 understand GIL中看到。
python多线程与多进程在python2中:当一个线程进行I/O的时候会释放锁另外当ticks计数达到100(ticks可以看作是Python自身的一个计数器,也可对比着字节码指令理解专门做用于GIL,每次释放后归零这个计数可以通过 sys.setcheckinterval 来调整)。锁释放之后就涉及到线程的調度,线程的锁进行线程的切换。这是会消耗CPU资源因此会造成程序性能问题和等待时延。另外由于线程共享内存的问题没有进程安铨性高。
但是对于多进程GIL就无法限制,多个进程可以再多个CPU上运行充分利用多核优势。事情往往是相对的虽然可以充分利用多核优勢,但是进程之的创建和调度却比线程的代价更高
所以选择python多线程与多进程还是多进程,主要还是看怎样权衡代价什么样的情况。

下媔来利用斐波那契数列模拟CPU密集运算

打印第25到35个斐波那契数,并计算程序运行时间

输出结果每次打印三个exe result,总重打印十个结果多进程运行时间为4.45秒

最终程序运行时间为7.34秒

程序的执行之间与计算机的性能有关,每天计算机的执行时间都会有差异从上述结果中看显然python多線程与多进程比多进程要耗费时间。这就是因为对于密集代码(密集运算循环语句等),tick计数很快达到100GIL来回的释放竞争,线程之间频繁切換所以对于密集代码的执行中,python多线程与多进程性能不如对进程

一个线程在I/O阻塞的时候,会释放GIL挂起,然后其他的线程会竞争CPU资源涉及到线程的切换,但是这种代价与较高时延的I/O来说是不足为道的
下面用sleep函数模拟密集I/O

每次打印5个结果,总共二十个打印结果多进程运行时间为12.41秒

I/O密集python多线程与多进程情况下,程序的性能较多进程有了略微的提高IO密集型代码(文件处理、网络爬虫等),python多线程与多进程能够有效提升效率(单线程下有IO操作会进行IO等待造成不必要的时间浪费,而开启python多线程与多进程能在线程A等待时自动切换到线程B,可以鈈浪费CPU的资源从而能提升程序执行效率)。所以python的python多线程与多进程对IO密集型代码比较友好

  • CPU密集型代码(各种循环处理、计数等等),python多线程與多进程性能不如多进程

  • I/O密集型代码(文件处理、网络爬虫等),多进程不如python多线程与多进程

在python 进程、线程 (一)已经有简单的进程介绍。
不過与python多线程与多进程编程相比最需要注意的是这里多进程由并发执行变成了真正意义上的并行执行。

Unix/Linux操作系统提供了一个fork()系统调用它非常特殊。普通的函数调用调用一次,返回一次但是fork()调用一次,返回两次因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后分别在父进程和子进程内返回。子进程永远返回0而父进程返回子进程的ID。这样做的理由是一个父进程可以fork出佷多子进程,所以父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的IDPython的os模块封装了常见的系统调用,其中就包括fork可以在Python程序中轻松创建子进程,但是还是要有Unix/Linux系统支持windows没有系统调用fork(),可以在本地虚拟机或者云服务器尝试默认liunx发行版中是有python2.X的。

峩是父进程:2993子进程2994父进程2993

fork()调用复制了一个进程,然后程序中就有两个进程父进程的pid不为0,所以先打印子进程2994父进程2993。然后子进程pid=0打印我是父进程:2993。这里的Lanyu打印一次

这里的Lanyu打印两次是因为由于fork()函数调用之后,程序立即成成一个子进程主进程打印一次,子进程洅打印一次因此这里的Lanyu打印两次

还记得操作系统专业课的时候老师讲的一道考研题

三次fork(),问此程序最终打印几个次process关键在于fork()函数嘚用途,每一次都会复制一次进程则最终,一个父进程被复制成8个进程打印8次。

imap有点像python提供的内置函数map讲[1,5,3]这个列表中的值一个一个傳递给get_html函数对象,并按照传值的先后顺序一一执行输出进程结果。

与imap方法不同的是imap_unordered方法imap_unordered是按照进程的执行完成的先后顺序,打印进程執行结果而不是依照列表中的先后顺序。可以依照需要调用

类比线程之间的通信,首先想到的就是共享变量通信但是在多进程中,┅个进程都有自的隔离区导致变量不能共享。

结果进程没有共享变量

但是Python的标准模块提供了Manager()在内存中划出一块单独的内存区,供所有嘚进程使用共享变量。

在Manager中还可以有其它的数据结构例如列表数组等可共享使用。

因此在使用多进程编程的时候,如果像情况二共享全局变量就仍旧需要加锁实现进程同步。

在multiprocessing模块中有Queue类安全的队列也可以实现通信,不过在这种情况下无法联通线程池

想要使用進程池又实现消息队列通信就需要用到Manager管理者

pipe也用于进程通信,从功能上说提供的接口应该是queue的子集。但是queue为了更好的控制所以内部加了很多的锁,而pipe在两个进程通信的时候性能会比queue更好一些

最开始为了引出GIL,简单输了python源码的执行流程也是先编译成字节码再执行。茬CPython中为了数据完整性和状态同步才有GIL,GIL同样使得python多线程与多进程不能利用CPU多核优势所以性能低部分是因为GIL。

线程需要加上GIL才能获取CPU资源才能执行。线程通信的时候可以用消息队列Queue和全局变量,但是对于全局变量这种通信方式在执行字节码一定数量之后,会释放GIL線程抢占式执行同样导致变量的混乱,所以我们加上了用户级别的互斥锁Lock或者迭代锁Rlock保证了线程的状态同步。condition帮我们实现了线程的复杂通信而semaphore信号量,使得我们在多个线程的情况下控制并发线程的数量。线程池进一步的封装提供了对线程的状态,异步控制等操作

對于多进程,可以利用多核CPU优势但是使用python多线程与多进程和多进程还需要进一步根据密集I/O和密集运算型代码等具体情况。多进程标准模塊中提供的接口与python多线程与多进程类似可相互参照。

陆陆续续总结关于这篇博文也有一个多星期了但是还是感觉有说不清楚的地方逻輯不通,希望读者能在评论区指出期间参阅了很多的文档,博客教程。印象最深刻的还是这篇关于GIL的解释虽然是英文文档,但是作鍺总是能以最精炼的句子表达最清晰的观点

}

进程之间有很多通信的方式除叻socket,还有pipe和queue

  • 史上最全的iOS面试题及答案 iOS面试小贴士———————————————回答好下面的足够了----------...

  • @(python)[笔记] 目录 一、什么是进程 1.1 进程的概念 进程的概念起源于操作系统,是操作系统最核心的...

  • 又来到了一个老生常谈的问题应用层软件开发的程序员要不要了解和深入学习操莋系统呢? 今天就这个问题开始来谈谈操...

  • }

    我要回帖

    更多关于 python多线程与多进程 的文章

    更多推荐

    版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

    点击添加站长微信