并发编程-多进程
并发编程-多进程
1.并发编程
并发指的是多个任务同时被执行,并发编程指的是编写支持多任务并发的应用程序。
2. 进程
进程指的是正在运行的程序,是一系列过程的统称,也是操作系统在调度和进行资源分配的基本单位.进程是实现并发的一种方式.
3. 进程与程序
进程是正在运行的程序,程序是程序员编写的一堆代码,也就是一堆字符,当这堆代码被系统加载到内存中并执行时,就有了进程。
需要注意的是:一个程序是可以产生多个进程的,就像我们可以同时运行多个QQ程序一样,会形成多个进程
4.PID和PPID
PID:系统会给每一个进程分配一个进程编号即PID,如同人需要一个身份证号来区分。
验证:
1.tasklist 用于查看所有的进程信息
2.taskkill /f /pid pid 该命令可以用于结束指定进程
PPID:当一个进程a开启了另一个进程b时,a称为b的父进程,b称为a的子进程
在python中可以通过os模块来获取父进程的pid.
如果是在pycharm中运行的py文件,那pycahrm就是这个python.exe的父进程,当然你可以从cmd中来运行py文件,那此时cmd就是python.exe的父进程
5.并发与并行,阻塞与非阻塞
并发指的是,多个事件同时发生
例如洗衣服和做饭,同时发生了,但本质上是两个任务在切换,给人的感觉是同时在进行,也被称为伪并行
并行指的是,多个事件同时进行着
例如一个人在写代码另一个人在写书,这两件事件是同时在进行的,要注意的是一个人是无法真正的并行执行任务的,在计算机中单核CPU也是无法真正并行的,之所以单核CPU也能同时运行qq和微信其实就是并发执行
阻塞与非阻塞指的是程序的状态
阻塞状态是因为程序遇到了IO操作,或是sleep,导致后续的代码不能被CPU执行
非阻塞与之相反,表示程序正在正常被CPU执行
补充:进程有三种状态
就绪态,运行态,和阻塞态
6.进程相关理论
而对于通用系统(跑很多应用程序),需要有系统运行过程中创建或撤销进程的能力,主要分为4中形式创建新的进程
- 系统初始化(查看进程linux中用ps命令,windows中用任务管理器,前台进程负责与用户交互,后台运行的进程与用户无关,运行在后台并且只在需要时才唤醒的进程,称为守护进程,如电子邮件、web页面、新闻、打印)
2. 一个进程在运行过程中开启了子进程(如nginx开启多进程,os.fork,subprocess.Popen等)
3. 用户的交互式请求,而创建一个新进程(如用户双击暴风影音) - 一个批处理作业的初始化(只在大型机的批处理系统中应用)
关于创建的子进程,UNIX和windows
1.相同的是:进程创建后,父进程和子进程有各自不同的地址空间(多道技术要求物理层面实现进程之间内存的隔离),任何一个进程的在其地址空间中的修改都不会影响到另外一个进程。
2.不同的是:在UNIX中,子进程的初始地址空间是父进程的一个副本,提示:子进程和父进程是可以有只读的共享内存区的。但是对于windows系统来说,会重新加载程序代码。
进程的销毁
- 正常退出(自愿,如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出,在linux中用exit,在windows中用ExitProcess)
- 出错退出(自愿,python a.py中a.py不存在)
- 严重错误(非自愿,执行非法指令,如引用不存在的内存,1/0等,可以捕捉异常,try...except...)
- 被其他进程杀死(非自愿,如kill -9)
进程的层次结构
无论UNIX还是windows,进程只有一个父进程,不同的是:
- 在UNIX中所有的进程,都是以init进程为根,组成树形结构。父子进程共同组成一个进程组,这样,当从键盘发出一个信号时,该信号被送给当前与键盘相关的进程组中的所有成员。
- 在windows中,没有进程层次的概念,所有的进程都是地位相同的,唯一类似于进程层次的暗示,是在创建进程时,父进程得到一个特别的令牌(称为句柄),该句柄可以用来控制子进程,但是父进程有权把该句柄传给其他子进程,这样就没有层次了。
7.Python实现多进程
- 实例化Process类
import os
from multiprocessing import Process
import time
def task(name):
print('%s is running'%name) #son is running
print(os.getpid(),os.getppid()) #12692 17576
if __name__ == '__main__':
# p = Process(target=task,args=('son',))
p = Process(target=task,kwargs={'name':'son'})
time.sleep(1)
p.start()
print('father process',os.getpid()) #father process,17576
linux 与windows开启进程的方式不同
linux 会将父进程的内存数据 完整copy一份给子进程
注意:
windows 会导入父进程的代码 从头执行一遍 来获取需要处理的任务
所以在编写代码时如果是windows一定要将开启进程的代码放main判断中
- 继承Process类 并覆盖run方法
from multiprocessing import Process
import os
#自定义进程对象
class Downloader(Process):
# def __init__(self,url,size,name):
# super().__init__()
# self.url = url
# self.size = size
# self.name = name
def run(self):
print(os.getpid())
pass
if __name__ == '__main__':
m = Downloader()
m.start()
print("parent over",os.getpid())
如果需要对进程对象进行高度自定义那就可以继承它
需要注意的是
1.在windows下 开启子进程必须放到__main__
下面,因为windows在开启子进程时会重新加载所有的代码造成递归创建进程
2.第二种方式中,必须将要执行的代码放到run方法中,子进程只会执行run方法其他的一概不管
3.start仅仅是给操作系统发送消息,而操作系统创建进程是要花费时间的,所以会有两种情况发送
a.开启进程速度慢于程序执行速度,先打印父进程 在打印task中的消息
b.开启进程速度快于程序执行速度,先打印task中的消息,在打印父进程
进程之间内存相互隔离
from multiprocessing import Process
import time
x=1000
def task():
global x
x=0
print('son',x)
if __name__ == '__main_
print(x) #1000
p=Process(target=task)
p.start() #son,0
time.sleep(5)
print(x) #1000
join函数的使用
# join的使用(利用join达到串行的效果)
from multiprocessing import Process
import time
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")
进程对对象的常用属性
from multiprocessing import Process
def task(n):
print('%s is running'%n)
if __name__ == '__main__':
p = Process(target=task,args=('wq',),name = 'liu_jin')
p.start() #启动子进程
print(p.pid) #18664(获取子进程的pid)
print(p.name) #获取子进程名
p.terminate() #终止子进程
p.join() #提高子进程优先级
print(p.is_alive()) #获取进程的存活状态(关键在与子进程是(true)否(false)被start)
孤儿进程和僵尸进程
什么是孤儿进程
孤儿进程指的是开启子进程后,父进程先于子进程终止了,那这个子进程就称之为孤儿进程
例如:qq聊天中别人发给你一个链接,点击后打开了浏览器,那qq就是浏览器的父进程,然后退出qq,此时浏览器就成了孤儿进程
孤儿进程是无害的,有其存在的必要性,在父进程结束后,其子进程会被操作系统接管。
什么是僵尸进程
僵尸进程指的是,当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。该情况仅在linux下出现。windows中进程间完全是独立的没有任何关联。
如果父进程先退出 ,子进程被操作系统接管,子进程退出后操作系统会回收其占用的相关资源!