Python - 进程
1|0什么是进程
进程:正在进行的一个过程或者说一个任务。而负责执行任务则是cpu。
2|0进程与程序的区别
程序仅仅只是一堆代码而已,而进程指的是程序的运行过程。
需要强调的是:同一个程序执行两次,那也是两个进程,比如打开播放器,虽然都是同一个软件,但是一个可以播放第一集,一个可以播放第二集。
3|0并发与并行
并行
: 并行是指两者同时执行,比如赛跑,两个人都在不停的往前跑;(资源够用,比如三个线程,四核的CPU )
并发
: 并发是指资源有限的情况下,两者交替轮流使用资源,比如一段路(单核CPU资源)同时只能过一个人,A走一段后,让给B,B用完继续给A ,交替使用,目的是提高效率。
区别
:
并行
是从微观上,也就是在一个精确的时间片刻,有不同的程序在执行,这就要求必须有多个处理器。
并发
是从宏观上,在一个时间段上可以看出是同时执行的,比如一个服务器同时处理多个session。
所有的这些进程都需被管理,于是一个支持多进程的多道程序系统
是至关重要的
多道技术概念:
内存中同时存入多道(多个)程序,cpu从一个进程快速切换到另外一个,使每个进程各自运行几十或几百毫秒,这样,虽然在某一个瞬间,一个cpu只能执行一个任务,但在1秒内,cpu却可以运行多个进程,这就给人产生了并行的错觉,即伪并发,以此来区分多处理器操作系统的真正硬件并行(多个cpu共享同一个物理内存)
4|0同步\异步and阻塞\非阻塞(重点)
4|1进程状态介绍
在了解其他概念之前,我们首先要了解进程的几个状态。在程序运行的过程中,由于被操作系统的调度算法控制,程序会进入几个状态:就绪,运行和阻塞。
(1)就绪(Ready)状态
当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。
(2)执行/运行(Running)状态当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。
(3)阻塞(Blocked)状态正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。
4|2同步、异步
同步比喻
异步比喻
4|3阻塞、非阻塞
阻塞比喻
非阻塞比喻
4|4关于unix和windows创建进程
1. 在UNIX中该系统调用是:fork,fork会创建一个与父进程一模一样的副本,二者有相同的存储映像、同样的环境字符串和同样的打开文件(在shell解释器进程中,执行一个命令就会创建一个子进程)
2. 在windows中该系统调用是:CreateProcess,CreateProcess既处理进程的创建,也负责把正确的程序装入新进程。
相同的是:
进程创建后,父进程和子进程有各自不同的地址空间(多道技术要求物理层面实现进程之间内存的隔离),任何一个进程的在其地址空间中的修改都不会影响到另外一个进程。
不同的是:
在UNIX中,子进程的初始地址空间是父进程的一个副本,提示:子进程和父进程是可以有只读的共享内存区的。但是对于windows系统来说,从一开始父进程与子进程的地址空间就是不同的。
5|0multiprocessing模块介绍
仔细说来,multiprocess不是一个模块而是python中一个操作、管理进程的包。 之所以叫multi是取自multiple的多功能的意思,在这个包中几乎包含了和进程有关的所有子模块。由于提供的子模块非常多,为了方便大家归类记忆,我将这部分大致分为四个部分:创建进程部分,进程同步部分,进程池部分,进程之间数据共享。
6|0Process类的介绍
创建进程的类:
参数介绍:
方法介绍
属性介绍
7|0Process类的使用
注意:在windows中Process()必须放到# if __name__ == '__main__':下
7|1创建开启子进程的两种方式
方式一
方式二
7|2验证进程之间的空间隔离
在一个进程中
在不同的进程中:
7|3进程对象的join方法
主进程自己的代码如果长,等待自己的代码执行结束
子进程的执行时间长,主进程会在主进程代码执行完毕之后等待子进程执行完毕之后,主进程才结束
7|4多个进程同时运行时join的作用
注意:
子进程的执行顺序不是根据启动顺序决定的
错误的使用
正确使用
7|5进程对象的其他属性(了解)
多进程中的其他方法(在主进程内结束一个子进程p.terminate(),检验一个进程是否还或者的状态p.is_alive())
结束一个进程不是在执行方法之后立即生效,需要一个操作系统响应的过程
7|6僵尸进程与孤儿进程
僵尸进程(有害)
一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。
详细描述如下:
因此,UNⅨ提供了一种机制可以保证父进程可以在任意时刻获取子进程结束时的状态信息:
1、在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)
2、直到父进程通过wait / waitpid来取时才释放. 但这样就导致了问题,如果进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。 如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。
孤儿进程(无害)
僵尸进程危害场景
测试
产生僵尸进程的程序test.py内容如下
在unix或linux系统上执行
解决方法
等待父进程正常结束后会调用wait/waitpid去回收僵尸进程
但如果父进程是一个死循环,永远不会结束,那么该僵尸进程就会一直存在,僵尸进程过多,就是有害的
解决方法一:杀死父进程
解决方法二:对开启的子进程应该记得使用join,join会回收僵尸进程
参考python2源码注释
join方法中调用了wait,告诉系统释放僵尸进程。discard为从自己的children中剔除
解决方法三:http://blog.csdn.net/u010571844/article/details/50419798
8|0守护进程
会随着主进程的结束而结束。
主进程创建守护进程
其一:守护进程会在主进程代码执行结束后就终止(如果这个主进程还有其他的子进程,守护进程不关心)
其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
注意:
进程之间是互相独立的,主进程代码运行结束,守护进程随即终止
下面这段代码没有守护进程,主进程结束后,子进程还是会不停的运行
下面这段代码为守护进程,主进程结束,子进程也结束.
经典例题
9|0进程同步控制(锁、信号量、事件)
9|1锁 —— multiprocessLock
多进程抢占输出资源
使用锁维护执行顺序
上面这种情况虽然使用加锁的形式实现了顺序的执行,但是程序又重新变成串行了,这样确实会浪费了时间,却保证了数据的安全。
接下来,我们以模拟抢票为例,来看看数据安全的重要性。
多进程同时抢购余票
使用锁来保证数据安全
加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。
虽然可以用文件共享数据实现进程间通信,但问题是:
- 效率低(共享数据基于文件,而文件是硬盘上的数据)
- 需要自己加锁处理
因此我们最好找寻一种解决方案能够兼顾:
-
效率高(多个进程共享一块内存的数据)
-
帮我们处理好锁问题.
这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。
队列和管道都是将数据存放于内存中
队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,
我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。
9|2信号量(multiprocess.Semaphore)
假设有20个人排队上厕所,但是只有4个门,这时候如果直接多进程,会20个人同时进去,显然不对,所以需要信号量。
9|3事件(multiprocess.Event)
一个信号可以使所有的进程都进入阻塞状态,也可以控制所有的进程解除阻塞。
方法
示例
红绿信号灯问题
10|0进程间通信(队列和管道)
10|1队列(multiprocess.Queue)
创建队列的类(底层就是以管道和锁定的方式实现):
参数介绍:
方法介绍:
主要方法
其他方法(了解)
简单使用:单看队列用法
上面这个例子还没有加入进程通信,只是先来看看队列为我们提供的方法,以及这些方法的使用和现象。
上面是一个queue的简单应用,使用队列q对象调用get函数来取得队列中最先进入的数据。 接下来看一个稍微复杂一些的例子:批量生产数据放入队列再批量取走
生产者消费者模型
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
基于队列实现生产者消费者模型
此时的问题是主进程永远不会结束,原因是:生产者p在生产完后就结束了,但是消费者c在取空了q之后,则一直处于死循环中且卡在q.get()这一步。
解决方式无非是让生产者在生产完毕后,往队列中再发一个结束信号,这样消费者在接收到结束信号后就可以break出死循环。
改良版1:生产者在生产完毕后发送结束信号None
注意:结束信号None,不一定要由生产者发,主进程里同样可以发,但主进程需要等生产者结束后才应该发送该信号。
改良版2:主进程在生产者生产完毕后发送结束信号None
但上述解决方式,在有多个生产者和多个消费者时,我们则需要用一个很low的方式去解决
多个消费者的例子:有几个消费者就需要发送几次结束信号
JoinableQueue([maxsize])
创建可连接的共享进程队列。这就像是一个Queue对象,但队列允许项目的使用者通知生产者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。
JoinableQueue队列实现消费之生产者模型
原理
10|2管道(multiprocess.Pipe)
了解即可,正常情况用队列
介绍
初使用
应该特别注意管道端点的正确管理问题。如果是生产者或消费者中都没有使用管道的某个端点,就应将它关闭。
这也说明了为何在 生产者中关闭了管道的输出端,在消费者中关闭管道的输入端
。如果忘记执行这些步骤,程序可能在消费者中的 recv()
操作上阻塞。管道是由操作系统进行引用计数的,必须在所有进程中关闭管道后才能生成 EOFError
异常。因此,在生产者中关闭管道不会有任何效果,除非消费者也关闭了相同的管道端点。
用管道也做生产者消费者模型
管道进程不安全,有可能多个进程同时取一个数据,所以 recv
要加锁。正常情况下用队列,不要用管道。
10|3进程之间的数据共享(multiprocess.Manager)(了解)
展望未来,基于消息传递的并发编程是大势所趋
即便是使用线程,推荐做法也是将程序设计为大量独立的线程集合,通过消息队列交换数据。
这样极大地减少了对使用锁定和其他同步手段的需求,还可以扩展到分布式系统中。
但进程间应该尽量避免通信,即便需要通信,也应该选择进程安全的工具来避免加锁带来的问题。
以后我们会尝试使用数据库来解决现在进程之间的数据共享问题。
--
manager也有问题,进程数据不安全。比如上面的例子,要减1,有可能两个进程同时减,本来应该减2的,但是由于同时减,结果只是减1。所以也要加锁。
11|0进程池和multiprocess.Pool模块
11|1进程池 (CPU核心 + 1)
为什么要有进程池?进程池的概念。
在程序实际处理问题过程中,忙时会有成千上万的任务需要被执行,闲时可能只有零星任务。那么在成千上万个任务需要被执行的时候,我们就需要去创建成千上万个进程么?首先,创建进程需要消耗时间,销毁进程也需要消耗时间。第二即便开启了成千上万的进程,操作系统也不能让他们同时执行,这样反而会影响程序的效率。因此我们不能无限制的根据任务开启或者结束进程。那么我们要怎么做呢?
在这里,要给大家介绍一个进程池的概念,定义一个池子,在里面放上固定数量的进程,有需求来了,就拿一个池中的进程来处理任务,等到处理完毕,进程并不关闭,而是将进程再放回进程池中继续等待任务。如果有很多任务需要执行,池中的进程数量不够,任务就要等待之前的进程执行任务完毕归来,拿到空闲进程才能继续执行。也就是说,池中进程的数量是固定的,那么同一时间最多有固定数量的进程在运行。这样不会增加操作系统的调度难度,还节省了开闭进程的时间,也一定程度上能够实现并发效果。
11|2multiprocess.Pool模块
参数
主要方法
其他方法
进程池和多进程效率对比
同步
异步
server:进程池版socket并发聊天
client
发现:并发开启多个客户端,服务端同一时间只有4个不同的pid,只能结束一个客户端,另外一个客户端才会进来.
回调函数
get返回结果
先提交后get
使用map(算是另外一种异步执行的东西)
回调函数 callback (不能传参,因为自动有参数了,是func1的返回值。回调函数在主进程中执行)
使用多进程请求多个url来减少网络等待浪费的时间
处理数据用回调函数,因为多个进程处理数据在主进程中变成串行,时间还是会远远短于取网络上爬取数据的时间(因为网络延迟)。所以处理数据在回调函数中
__EOF__

本文链接:https://www.cnblogs.com/dongye95/p/14787249.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2018-05-19 Python 常用模块