并发编程
并发编程
并发指的是多个任务同时被执行,并发编程指的是编写出支持多任务并发的应用程序所在.
在我们写的TCP通讯中,服务器建立连接后需要一个循环来重复收发文件和下载文件的过程,但是服务器并不知道客户端会在什么时候发来数据,这样服务器就会一直处在于一个等待的状态,此时的服务器无法与外界客户端相连接,所以并发编程就应运而生了,并发编程的原理就是让程序处理多个任务,且多个任务可同时运行
什么是进程
进程指的是正在运行的程序,一台电脑可以同时运行多个进程,这些同时运行的进程是由操作系统来统一分配资源,那么在运行多个程序的电脑就是在多进程进行任务
进程指的是并发的一种方式,在学习并发变成之前要先了解进程的基本概念以及多进程的实现原理,这就不得不提到操作系统了,因为进程的概念来自于操作系统,指的是在操作系统中运行的程序
进程与程序
程序是正在运行的程序,也就是一堆代码,当它被加载到CPU中去执行的时候,就有了进程这个概念
需要注意的是:一个程序可以产生多个进程(方法就是运行多个相同的程序,比如QQ双开,多开)
PID和PPID
PID
计算机操作系统会给每个正在运行的程序分配一个进程编号PID,这个编号是随机的且都不相同(即使是两个相同的程序双开,他们的PID也是不同的) WINDOWS中可以再CMD终端tasklist查看
PPID
当一个进程a开启了另一个进程b时,a就可以称之为b的父进程,b称之为a的子进程(比如打开QQ中的网络链接浏览器工作了起来,那么QQ就是爸爸,浏览器就是儿子)
# 在python 中可以使用os模块中的getpid()来获取自己的PID,getppid()来获取自己父进程的pid
inport os
print("self",os.getpid())
print("parent",os.getppid())
并发与并行,阻塞与非阻塞
并发:指的是多个事件同时发生(比如一边搬砖一边砌墙,本质上做着两件事情,但是确是在两件事情之间来回切换着))
并行:指的是多个事件同时进行着(比如一边拉屎一边吃饭,这是真正意义上的同时做两件事情)
阻塞:阻塞状态指的是在程序运行时遇到了IO操作,或者是sleep(),导致代码不能继续向下运行,原地停滞等待
非阻塞:程序执行时,没有遇到IO操作,正在愉快的往下进行着
进程的三种状态:就绪态,运行态,阻塞态
进程相关理论知识
进程的创建
但凡是硬件,都需要有操作系统去管理,只要有操作系统,就有进程的概念,就需要有创建进程的方式,一些操作系统只为一个应用程序设计,比如微波炉中的控制器,一旦启动微波炉,进程就已经存在。
而对于通用系统(跑很多应用程序),需要有系统运行过程中创建或撤销进程的能力,主要分为4中形式创建新的进程
1. 系统初始化(查看进程linux中用ps命令,windows中用任务管理器,前台进程负责与用户交互,后台运行的进程与用户无关,运行在后台并且只在需要时才唤醒的进程,称为守护进程,如电子邮件、web页面、新闻、打印)
2. 一个进程在运行过程中开启了子进程(如nginx开启多进程,os.fork,subprocess.Popen等)
3. 用户的交互式请求,而创建一个新进程(如用户双击暴风影音)
4. 一个批处理作业的初始化(只在大型机的批处理系统中应用)
无论哪一种,新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的:
1. 在UNIX中该系统调用是:fork,fork会创建一个与父进程一模一样的副本,二者有相同的存储映像、同样的环境字符串和同样的打开文件(在shell解释器进程中,执行一个命令就会创建一个子进程)
2. 在windows中该系统调用是:CreateProcess,CreateProcess既处理进程的创建,也负责把正确的程序装入新进程。
关于创建的子进程,UNIX和windows
1.相同的是:进程创建后,父进程和子进程有各自不同的地址空间(多道技术要求物理层面实现进程之间内存的隔离),任何一个进程的在其地址空间中的修改都不会影响到另外一个进程。
2.不同的是:在UNIX中,子进程的初始地址空间是父进程的一个副本,提示:子进程和父进程是可以有只读的共享内存区的。但是对于windows系统来说,会重新加载程序代码。
进程的销毁
- 正常退出(自愿,如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出,在linux中用exit,在windows中用ExitProcess)
2. 出错退出(自愿,python a.py中a.py不存在)
3. 严重错误(非自愿,执行非法指令,如引用不存在的内存,1/0等,可以捕捉异常,try...except...)
4. 被其他进程杀死(非自愿,如kill -9)
进程的层次结构
无论UNIX还是windows,进程只有一个父进程,不同的是:
1. 在UNIX中所有的进程,都是以init进程为根,组成树形结构。父子进程共同组成一个进程组,这样,当从键盘发出一个信号时,该信号被送给当前与键盘相关的进程组中的所有成员。
2. 在windows中,没有进程层次的概念,所有的进程都是地位相同的,唯一类似于进程层次的暗示,是在创建进程时,父进程得到一个特别的令牌(称为句柄),该句柄可以用来控制子进程,但是父进程有权把该句柄传给其他子进程,这样就没有层次了。
python中开启子进程的两种方式
方法1:实例化Process对象
from multiprocessing import Process
import time
def task(name):
print('%s is running' %name)
time.sleep(3)
print('%s is done' %name)
if __name__ == '__main__':
# 在windows系统之上,开启子进程的操作一定要放到这下面
# Process(target=task,kwargs={'name':'egon'})
p=Process(target=task,args=('jack',))
p.start() # 向操作系统发送请求,操作系统会申请内存空间,然后把父进程的数据拷贝给子进程,作为子进程的初始状态
print('======主')
方法2:继承Process类,并覆盖Run方法
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self,name):
super(MyProcess,self).__init__()
self.name=name
def run(self):
print('%s is running' %self.name)
time.sleep(3)
print('%s is done' %self.name)
if __name__ == '__main__':
p=MyProcess('jack')
p.start()
print('主')
({注意}):
-
在windows下 开启子进程必须放到
__main__
下面,因为windows在开启子进程时会重新加载所有的代码造成递归创建进程 -
第二种方式中,必须将要执行的代码放到run方法中,子进程只会执行run方法其他的一概不管
-
start仅仅是给操作系统发送消息,而操作系统创建进程是要花费时间的,所以会有两种情况发送
-
开启进程速度慢于程序执行速度,先打印”主“ 在打印task中的消息
-
开启进程速度快于程序执行速度,先打印task中的消息,在打印”主“
进程间内存相互隔离
from multiprocessing import Process
import time
x=1000
def task():
global x
x=0
print('儿子死啦',x)
if __name__ == '__main_
print(x)
p=Process(target=task)
p.start()
time.sleep(5)
print(x)
join()函数
调用start函数后的操作就由操作系统来玩了,至于何时开启进程,进程何时执行,何时结束都与应用程序无关,所以当前进程会继续往下执行,join函数就可以是父进程等待子进程结束后继续执行
案例1
from multiprocessing import Process
import time
x=1000
def task():
time.sleep(3)
global x
x=0
print('儿子死啦',x)
if __name__ == '__main__':
p=Process(target=task)
p.start()
p.join() # 让父亲在原地等
print(x)
案例2
from multiprocessing import Process
import time,random
x=1000
def task(n):
print('%s is runing' %n)
time.sleep(n)
if __name__ == '__main__':
start_time=time.time()
p1=Process(target=task,args=(1,))
p2=Process(target=task,args=(2,))
p3=Process(target=task,args=(3,))
p1.start()
p2.start()
p3.start()
p3.join()
p1.join()
p2.join()
print('主',(time.time() - start_time))
start_time=time.time()
p_l=[]
for i in range(1,4):
p=Process(target=task,args=(i,))
p_l.append(p)
p.start()
for p in p_l:
p.join()
print('主',(time.time() - start_time))+
Process对象的常用属性
rom multiprocessing import Process
def task(n):
print('%s is runing' %n)
time.sleep(n)
if __name__ == '__main__':
start_time=time.time()
p1=Process(target=task,args=(1,),name='任务1')
p1.start() # 启动进程
print(p1.pid) # 获取进程pid
print(p1.name) # 获取进程名字
p1.terminate() # 终止进程
p1.join() # 提高优先级
print(p1.is_alive()) # 获取进程的存活状态
print('主')
孤儿进程与僵尸进程
什么是亚索进程
孤儿进程指的是开启子进程后,父进程先于子进程终止了,那这个子进程就称之为孤儿进程
例如:qq聊天中别人发给你一个链接,点击后打开了浏览器,那qq就是浏览器的父进程,然后退出qq,此时浏览器就成了孤儿进程
孤儿进程是无害的,有其存在的必要性,在父进程结束后,其子进程会被操作系统接管。
什么是僵尸进程
僵尸进程指的是,当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。该情况仅在linux下出现。windows中进程间完全是独立的没有任何关联。
如果父进程先退出 ,子进程被操作系统接管,子进程退出后操作系统会回收其占用的相关资源!
僵尸进程的危害
由于子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束. 那么会不会因为父进程太忙来不及wait子进程,或者说不知道 子进程什么时候结束,而丢失子进程结束时的状态信息呢? 不会。因为UNⅨ提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就必然可以得到。这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放. 但这样就导致了问题,如果进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生[僵死进程],将因为没有可用的进程号而导致系统不能产生新的进程. 此为僵尸进程的危害,应当避免。
好消息是:python已经帮我们自动处理了僵尸进程的回收工作
好消息好消息:Python解释器会自动回收僵尸进程!!!