并发编程
串行、并发和并行
串行:程序自上而下的顺序执行,必须把当前任务执行完毕才能执行下一个任务,无论当前任务需要多长时间
并发:多个任务同时执行。但是本质上是不同进程间的切换执行,由于切换速度非常快所以感觉是同时运行。学习并发的目的就是编写可以同时执行多个任务的程序,来提高效率
并行:多个任务真正的同时运行,必须具备多核CPU,有几个核心就能并行几个任务,当任务数量超过核心数还是并发执行
以上三个概念都是用于描述处理任务的方式
阻塞和非阻塞
阻塞:指的是程序遇到IO操作,如input(),无法继续执行代码时的一种状态
非阻塞:指的是程序没有遇到IO操作的一种状态。又分为运行和就绪两种状态
阻塞和非阻塞是用来描述程序的状态,可以用一些手段将阻塞的操作变成非阻塞的操作
进程与多进程
进程:指的是正在运行的程序,是操作系统在调度和进行资源分配的基本单位。当把一个程序从硬盘读入到内存时,进程就产生了
多进程:指的是同一时间有多个程序被装入内存并执行,实现原理就是操作系统调度进程的原理
进程和程序
程序:是一堆代码放在一个文件中,文件通常后缀为exe。在linux系统中没有后缀
进程:是将代码从硬盘读取到内存然后执行产生的。本质上就是一个程序在一个数据集上的一次动态执行过程。一般由程序、数据集(程序运行时产生的数据)、进程控制块三部分组成。
注意:一个程序可以产生多个进程,每一个进程都具备一个PID(进程编号),且是唯一的
进程的创建和销毁
创建
- 用户的交互式请求,如鼠标双击
- 由一个正在运行的程序调用了开启进程的接口,如subprocess
- 一个批处理作业的开始
- 系统初始化
销毁
- 任务完成——>自愿退出
- 强制结束,在windows系统中用taskkill命令,在linux系统中用kill命令——>非自愿退出
- 程序遇到了异常——>自愿退出
- 严重错误,比如访问了不该访问的内存
进程的层次结构
例如:在QQ中开启了浏览器,那么QQ是父进程,浏览器是子进程
在linux系统中,进程具备父子关系,是一个树状结构,可以相互查找到对方
在windows系统中,进程没有层级关系,父进程可以将子进程的句柄转让
PID和PPID
PID:当前进程的编号,可用os.getpid()
获得
PID:是父进程的编号,可用os.getppid()
获得
注意:我们在运行py文件时,其实运行的是python解释器
python如何使用多进程
创建子进程的方式一
导入multiprocessing中的Process类,实例化这个类,指定要执行的任务target
import os
from multiprocessing import Process
def task(name):
# 子进程
print(f'Son {name} process:', os.getpid())
print(f'{name} parent process:', os.getppid())
# 开启进程的代码必须放在__main__判断下面
if __name__ == '__main__':
# 实例化一个进程对象,并用函数来指定子进程要做的事情
p = Process(target=task, args=("nick",)) # args是传入字子进程中的参数
p.start() # 开启子进程
print('Parent process:', os.getpid())
-----------------------------------------------------
Parent process: 32692
Son nick process: 17508
nick parent process: 32692
创建子进程的方式二
导入multiprocessing中的Process类,继承这个类,覆盖run方法,将要执行的任务放入run中,开启进程时会自动执行该函数
import os
from multiprocessing import Process
class MyProcess(Process):
# 如果需要对进程对象进行高度自定义,就可以继承它
# def __init__(self,url,size,name):
# super().__init__()
# self.url = url
# self.size = size
# self.name = name
def run(self):
print('Son process:', os.getpid())
print('Its parent process:', os.getppid())
if __name__ == '__main__':
p = MyProcess()
p.start() # 启动进程时会自动执行run函数
print('Parent process:', os.getpid())
-----------------------------------------------------
Parent process: 17488
Son process: 34016
Its parent process: 17488
注意:linux与windows系统开启进程的方式不同,linux会将父进程的内存数据完整的复制一份给子进程,windows会导入父进程的代码从头执行一遍来获取需要处理的任务,所以在编写代码时windows系统一定要将开启进程的代码放在main判断中,linux可以不放
进程之间内存相互隔离
import time
from multiprocessing import Process
a = 257
def task():
global a
print('Son process:', id(a))
if __name__ == '__main__':
p = Process(target=task)
# 进程之间的内存相互隔离,在子进程中修改a的大小不会影响父进程中的a
print('Parent process old:', id(a))
p.start()
time.sleep(10)
print('Parent process new:', id(a))
-----------------------------------------------------
Parent process old: 2117832912592
Son process: 2075741633392
Parent process new: 2117832912592
join函数:让主进程等待子进程执行完毕再继续执行
# 一个子进程
from multiprocessing import Process
import time
def task1(name):
for i in range(100):
print("%s run" % name)
if __name__ == '__main__': # args 是给子进程传递的参数 必须是元组
p1 = Process(target=task1,args=("p1",))
p1.start() # 向操作系统发送指令
p1.join() # 让主进程 等待子进程执行完毕再继续执行
p1.join()
print("over")
-----------------------------------------------------
# 多个子进程
from multiprocessing import Process
import time
def task1(name):
for i in range(10000):
print("%s run" % name)
def task2(name):
for i in range(100):
print("%s run" % name)
if __name__ == '__main__': # args 是给子进程传递的参数,必须是元组
p1 = Process(target=task1,args=("p1",))
p1.start() # 向操作系统发送指令
p2 = Process(target=task2,args=("p2",))
p2.start() # 向操作系统发送指令
p2.join() # 让主进程等待子进程执行完毕再继续执行
p1.join()
# 需要达到的效果是:必须保证两个子进程是并发执行的,并且 over一定是在所有任务执行完毕后执行
print("over")
-----------------------------------------------------
# join的使用
from multiprocessing import Process
def task1(name):
for i in range(10):
print("%s run" % name)
if __name__ == '__main__': # args 是给子进程传递的参数 必须是元组
ps = []
for i in range(10):
p = Process(target=task1, args=(i,))
p.start()
ps.append(p)
# 挨个join以下
for i in ps:
i.join()
print("over")
进程对象的常用属性
属性 | 描述 |
---|---|
p.name | 进程名字,可以在实例化对象是为进程设置name参数 |
p.daemon | 守护进程 |
p.exitcode | 进程的退出码,在进程的exit()函数中传入的值 |
p.is_alive() | 进程是否存活 |
p.pid() | 进程id(子进程) |
p.terminate() | 终止进程,与start相同不会立即终止,操作系统需要时间 |
p.start() | 启动进程 |
僵尸进程与孤儿进程
孤儿进程:当父进程已经结束,而子进程还在运行,子进程就称为孤儿进程。有其存在的必要性,没有不良影响
僵尸进程:当一个进程已将结束了,但是它仍然还有一些数据存在,此时称之为僵尸进程
在linux中有这么一个机制,父进程无论什么时候都可以获取到子进程的一些数据。子进程在任务执行完毕后,确实结束了但是仍然保留一些数据,目的是为了让父进程能够获取这些信息。
在linux中可以调用waitpid来彻底清除子进程的残留信息
在python中已经封装了处理僵尸进程的操作,所以无需关心