Python--进程
一、背景知识
顾名思义,进程即正在执行的一个过程。进程是对正在运行程序的一个抽象。 是系统进行资源分配和调度的最基本单位。
进程起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一。操作系统的其它所有内容都是围绕进程的概念展开的。
必备的理论知识:
操作系统的作用:
1. 隐藏丑陋复杂的硬件接口,提供良好的抽象接口。
2. 管理、调度进程,并且将多个进程对硬件的竞争变得有序。
多道技术:
1. 产生背景: 针对单核,实现并发。
ps:
现在的主机一般都是多核,那么每个核都会利用多道技术
有4个cpu,运行于cpu1的某个程序遇到I/O阻塞,会等到I/O结束再重新调度,会被调度到4个cpu中的任意一个,具体有操作系统调度的算法决定。
2. 空间上的复用: 如内存中同时有多道程序
3. 时间上的复用:复用一个cpu的时间片
强调:遇到i/o,占用cpu时间过长也切,核心在于切之前将进程的状态保存下来,这样才能保证下次切回来时,能基于上次切走的位置继续运行。
二、 什么是进程
狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being excuted)
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,它是操作系统动态执行的最基本单元。
第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)(python的文件)、数据区域(data region)(python文件中定义的一些变量数据)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。 第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。[3] 进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。
动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。 并发性:任何进程都可以同其他进程一起并发执行 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位; 异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进 结构特征:进程由程序、数据和进程控制块三部分组成。 多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。
程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。 而进程是程序在处理机上的一次执行过程,它是一个动态的概念。 程序可以作为一种软件资料长期存在,而进程是有一定生命期的。 程序是永久的,进程是暂时的。 举例:就像qq一样,qq是我们安装在自己电脑上的客户端程序,其实就是一堆的代码文件,我们不运行qq,那么他就是一堆代码程序,当我们运行qq的时候,这些代码运行起来,就成为一个进程了。
三、进程调度
要想多个进程交替运行,操作系统必须对这些进程进行调度,这个调度也不是随即进行的,而是要遵循一定的法则,由此就有了进程的调度算法。
关于各种算法。。。 回头来研究,先心里有这么个概念吧。。。 无力。。。。 ……——……
四、并发与并行
通过进程之间的调度,也就是进程之间的切换,我们用户感知到好像是两个视频文件同时在播放,或者音乐和游戏同时在进行,我们来看一下什么叫做并发和并行。
无论是并发还是并行,在用户看来都是同时进行的,不管是进程还是线程,都只是一个任务而已,真实干活的是cpu,cpu来做这些任务,一个cpu同一时刻只能执行一个任务。
并发: 是伪并行,即看起来是同时运行。单个cpu+多道技术就可以实现并发
并行:同时运行,只有具备多个cpu才能实现并行。
单核下,可以利用多道技术;多个核,每个核也可以利用多道技术(多道技术是针对单核而言的)
有4个核,6个任务,这样同一时间有四个任务被执行,假设分别分配给了cpu1, cpu2,cpu3, cpu4,一旦任务1遇到I/O就被迫中断执行,此时任务5就拿到cpu1的时间片去执行,这就是单核下的多道技术;而一旦任务1的I/0结束,操作系统会重新调用它,可能会被分配给4个cpu中的任意一个去执行。
多道技术回顾:内存中同时存入多道(多个)程序,cpu从一个进程快速切换到另外一个,使每个进程各自运行几十或者几百毫秒,这样,虽然在某一个瞬间一个cpu只能执行一个任务,但在1秒内,cpu却可以运行多个进程,这就给人产生了并行的错觉,即伪并行,以此来区分多处理器操作系统的真正硬件并行(多个cpu共享同一个物理内存)
五、同步/异步/阻塞/非阻塞(重点)
1. 进程状态介绍
在程序运行的过程中,由于被操作系统的调度算法控制,程序会进入几个状态: 就绪、运行、阻塞
1)就绪(Ready): 当进程已经分配到除cpu以外的所有必要的资源,只要获得处理机会便可以立刻执行,这时候的状态称为就绪
2)执行/运行(Running):当进程已经获得处理机,其程序正在处理机上运行,称为执行状态
3) 阻塞(Blocked)状态正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。 例如: 等I/O完成、申请缓冲区不能满足....
2. 同步/异步
所谓的同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,两个任务的状态可以保持一致。其实就是一个程序结束才执行另外一个程序,串行的,不一定两个程序就有依赖关系。
所谓异步就是不需要等待被依赖的任务完成,只是通知被依赖的任务需要完成什么工作,依赖的任务也立即执行,只要自己完成整个任务就算完成了;至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以是不可靠的任务序列。
3. 阻塞与非阻塞
这两个概念与程序(线程)等待消息通知(无所谓同步或者异步)时的状态有关;主要是程序(线程)等待通知消息时的状态角度来说的。
阻塞的方法:input、time.sleep,socket中的recv、accept等等。
以上理论知识有时间还是要认真看一遍,消化一下。 来吧。终于进入实战。。。。
1. process模块介绍
process模块是一个创建进程的模块,借助整个模块,就可以完成进程的创建
写一个程序来看看:
#当前文件名称为test.py
# from multiprocessing import Process
#
# def func():
# print(12345)
#
# if __name__ == '__main__': #windows 下才需要写这个,这和系统创建进程的机制有关系,不用深究,记着windows下要写就好啦
# #首先我运行当前这个test.py文件,运行这个文件的程序,那么就产生了进程,这个进程我们称为主进程
#
# p = Process(target=func,) #将函数注册到一个进程中,p是一个进程对象,此时还没有启动进程,只是创建了一个进程对象。并且func是不加括号的,因为加上括号这个函数就直接运行了对吧。
# p.start() #告诉操作系统,给我开启一个进程,func这个函数就被我们新开的这个进程执行了,而这个进程是我主进程运行过程中创建出来的,所以称这个新创建的进程为主进程的子进程,而主进程又可以称为这个新进程的父进程。
#而这个子进程中执行的程序,相当于将现在这个test.py文件中的程序copy到一个你看不到的python文件中去执行了,就相当于当前这个文件,被另外一个py文件import过去并执行了。
#start并不是直接就去执行了,我们知道进程有三个状态,进程会进入进程的三个状态,就绪,(被调度,也就是时间片切换到它的时候)执行,阻塞,并且在这个三个状态之间不断的转换,等待cpu执行时间片到了。
# print('*' * 10) #这是主进程的程序,上面开启的子进程的程序是和主进程的程序同时运行的,我们称为异步
上面说了,我们通过主进程创建的子进程是异步执行的,那么我们来验证一下,并且看一下子进程和主继承的id号
os.getpid():获取自己进程的id号
os.getppid() : 获取自己进程的父进程的ID号
1 import time 2 import os 3 from multiprocessing import Process 4 5 def func(): 6 print("aaa") 7 time.sleep(1) 8 print("子进程>>",os.getpid()) 9 print("该子进程的父进程>>",os.getppid()) 10 print(12345) 11 12 if __name__ == '__main__': 13 # 首先我们要运行当前这个文件,运行的这个文件的程序就产生了进程 14 15 p = Process(target=func,) 16 p.start() 17 print("*" * 10) 18 print("父进程>>",os.getpid()) 19 print("父进程的父进程>>",os.getppid() )
一个进程的生命周期:如果子进程的运行时间长,那么等到子进程执行结束,程序才结束
如果主进程的运行时间长,那么主进程执行结束程序才结束
如果说一个主进程运行完了之后,我们把pycharm关了,但是子进程还没有执行结束,那么子进程还存在吗?这要看你的进程是如何配置的,如果说我们没有配置说我主进程结束,子进程要跟着结束,那么主进程结束的时候,子进程是不会跟着结束的,他会自己执行完,如果我设定的是主进程结束,子进程必须跟着结束,那么就不会出现单独的子进程(孤儿进程)了,具体如何设置,看下面的守护进程的讲解。比如说,我们将来启动项目的时候,可能通过cmd来启动,那么我cmd关闭了你的项目就会关闭吗,不会的,因为你的项目不能停止对外的服务。
Process 类中参数的介绍:
1. group参数未使用,值始终为None
2. target表示调用对象,即子进程要执行的任务。
3. args 表示调用对象的位置参数元祖,args=(1,2,"egon")
4. kwargs 表示调用对象的字典,kwargs={"name":"egon","age":18}
5. name 为子进程的名称
给要执行的函数传参数:
1 import time 2 from multiprocessing import Process 3 4 def func(x,y): 5 print(x) 6 time.sleep(1) 7 print(y) 8 9 if __name__ == '__main__': 10 p = Process(target=func,args=("姑娘","来玩啊!")) 11 p.start() 12 # time.sleep(2) # 等待子进程执行结束,再执行主进程 13 print("父进程执行结束")
Process类中各方法的介绍:
1. p.start(): 启动进程,并调用该子进程中的p.run()
2. p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
3. p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建类子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况,如果p还保存了一个锁,那么也将不会被释放,进而导致死锁。
4. p.is_alive():如果p仍然进行,返回True
5. p.join([timeout]):主进程等待p终止(强调:是主进程处于等的状态,而p是处于运行的状态),timeout是可选的超时时间,p.join只能join住start开启的进程,而不能join住run开启的进程
怎么样开启多个进程呢?for循环。并且我有个需求就是说,所有的子进程异步执行,然后所有的子进程全部执行完之后,我再执行主进程,怎么搞?看代码
1 import time 2 import os 3 from multiprocessing import Process 4 5 def func(x,y): 6 print(x) 7 time.sleep(1) # 进程切换 8 print(y) 9 10 if __name__ == '__main__': 11 p_list = [] 12 for i in range(10): 13 p = Process(target=func,args=("姑娘%s"%i,"来玩啊")) 14 p_list.append(p) 15 p.start() 16 [ap.join() for ap in p_list] # 这个是解决方案,所有的子进程都去执行了,一次给所有正在执行的子进程加上join(),主进程就会等待所有的子进程结束后在执行自己的程序,并且保障了所有的子进程都是异步执行。 17 print("不要钱")
2. 进程的创建第二种方法(继承)
1 import os 2 import time 3 from multiprocessing import Process 4 5 class MyProcess(Process): 6 # 我们通过init方法可以传参数,如果只写一个run方法,没办法传参数, 7 # 因为创建对象的是传参就是在init方法里面 8 def __init__(self,person): 9 super().__init__() 10 self.person=person 11 def run(self): 12 print(os.getpid()) 13 print(self.pid) 14 # print(self.pid) 15 print("%s 正在和女主播聊天"%self.person) 16 17 if __name__ == '__main__': 18 p1=MyProcess("jedan") 19 p2=MyProcess("太白") 20 p3=MyProcess("alexDSB") 21 22 p1.start() # start内部会自动调用run方法 23 p2.start() 24 p3.start() 25 26 p1.join() 27 p2.join() 28 p3.join()
3. 进程之间的数据是隔离的:也就是数据不共享
1 from multiprocessing import Process 2 3 n=100 4 def work(): 5 global n 6 n=0 7 print("子进程内: ",n) 8 9 if __name__ == '__main__': 10 p = Process(target=work) 11 p.start() 12 p.join() # 等待子进程执行完毕,如果数据共享的话,子进程就通过global将n改为0了,否则,证明他们之间的数据是隔离的,互不影响 13 print("主进程内: ",n) 14 15 # 程序运行结果 16 # 子进程内: 0 17 # 主进程内: 100
练习:通过多进程来实现 服务端同时和多个客户端进行通信(这里先不考虑socketserver)
1 from socket import * 2 from multiprocessing import Process 3 4 def talk(conn,client_addr): 5 while True: 6 try: 7 msg =conn.recv(1024) 8 print("from_client_msg: ",msg) 9 if not msg:break 10 conn.send(msg.upper()) 11 except Exception: 12 break 13 if __name__ == '__main__': 14 server = socket(AF_INET,SOCK_STREAM) 15 ip_port = ("127.0.0.1",8090) 16 server.bind(ip_port) 17 server.listen(5) 18 while True: 19 conn,client_addr=server.accept() 20 p=Process(target=talk,args=(conn,client_addr)) 21 p.start()
1 from socket import * 2 3 client= socket(AF_INET,SOCK_STREAM) 4 ip_port = ("127.0.0.1",8090) 5 client.connect(ip_port) 6 7 while True: 8 msg = input("send_to_server: ").strip() 9 if not msg:break 10 11 client.send(msg.encode("utf-8")) 12 msg=client.recv(1024) 13 print(msg.decode("utf-8"))
上面我们通过多进程实现了并发,但是有个问题:
每来一个客户端,都在服务端开启一个进程,如果并发来一个万个客户端,要开启一万个进程吗,你自己尝试着在你自己的机器上开启一万个,10万个进程试一试。 解决方法:进程池
4. 进程的其它方法: terminate is_alive name pid
terminate 和 is_alive:
import time from multiprocessing import Process def func1(): time.sleep(2) print(1111) print("子进程") if __name__ == '__main__': p1=Process(target=func1,) p1.start() p1.terminate() # 给操作系统发了一个关闭p1子进程的信号,关闭进程 time.sleep(1) print("子进程是否还活着:",p1.is_alive()) # 是: 返回True; 否:返回False print(p1.pid) print("主进程结束")
name 和 pid
1 import time 2 import random 3 from multiprocessing import Process 4 5 class Piao(Process): 6 def __init__(self,name): 7 # 为我们开启的进程设置名字 8 super().__init__() 9 self.name=name 10 11 def run(self): 12 print("%s is piaoing"%self.name) 13 time.sleep(random.randint(1,3)) 14 15 p=Piao("egon") 16 p.start() 17 print("开始") 18 print(p.pid) # 查看pid
5. 守护进程
守护的子进程跟着主进程走。<之前我们将的子进程不会跟着主进程的结束而结束,子进程会全部执行完之后,程序才结束>
主进程创建守护进程
其一:守护进程会在主进程代码执行结束后就终止
其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
1 import os 2 import time 3 from multiprocessing import Process 4 5 class Myprocess(Process): 6 7 def __init__(self,person): 8 super().__init__() 9 self.person=person 10 11 def run(self): 12 print(os.getpid(),self.name) 13 print("%s正在和女主播聊天" %self.person) 14 time.sleep(3) 15 16 if __name__ == '__main__': 17 p=Myprocess("太白") 18 p.daemon=True # 一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p即终止运行。 19 p.start() 20 # time.sleep(1) 21 print("主进程结束")
6. 进程锁
ticket_lock = Lock()#创建锁 .acquire()#加锁, .release()#解锁
同步锁的作用:#加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。 # 虽然可以用文件共享数据实现进程间通信,但问题是:
# 1.效率低(共享数据基于文件,而文件是硬盘上的数据) # 2.需要自己加锁处理
1 import json 2 import time 3 import random 4 from multiprocessing import Process,Lock 5 6 def get_ticket(i,ticket_lock): 7 print("我们都到齐了,大家预备!!123") 8 time.sleep(1) 9 #所有代码 异步执行,到这里等待,同时再去抢下面的代码执行 10 ticket_lock.acquire() 11 #这里有个门,只有一个人能够抢到这个钥匙,加锁 12 with open("ticket","r") as f: 13 last_ticket_info = json.load(f) 14 #将文件数据load为字典类型的数据 15 last_ticket = last_ticket_info["count"] 16 print(last_ticket) 17 #查看一下余票的信息 18 if last_ticket > 0: 19 #如果看到余票大于零,说明你可以抢到票 20 time.sleep(random.random()) 21 #模拟网络延迟时间 22 last_ticket = last_ticket - 1 23 last_ticket_info["count"] = last_ticket 24 with open("ticket","w") as f: 25 #将修改后的参数写回文件 26 json.dump(last_ticket_info,f) 27 print("%s号抢到了,丫nb!" % i) 28 else: 29 print("%s号傻逼,没票了,明年再来" % i) 30 ticket_lock.release() 31 if __name__ == "__main__": 32 ticket_lock = Lock() 33 #创建一个进程锁 34 for i in range(10): 35 p = Process(target=get_ticket, args=(i, ticket_lock)) 36 p.start()
7. 队列
进程彼此之间互相隔离,要实现进程间通信,multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的,队列就像一个特殊的列表,但是可以设置固定长度,并且从前面插入数据,从后面取出数据,先进先出。
# 遵循先进先出的原则 q = Queue(3) 创建3个队列 q.put()发送数据 q.get()接受数据
1 q = Queue([maxsize]) #创建共享的进程队列 2 3 q.get( [ block [ ,timeout ] ] ) #返回q中的一个项目。如果q为空,此方法将阻塞,直到队列中有项目可用为止。block用于控制阻塞行为,默认为True. 如果设置为False,将引发Queue.Empty异常(定义在Queue模块中)。timeout是可选超时时间,用在阻塞模式中。如果在制定的时间间隔内没有项目变为可用,将引发Queue.Empty异常。 4 5 q.get_nowait( ) #和q.get(False)方法,一样 6 7 q.put(item [, block [,timeout ] ] ) #将item放入队列。如果队列已满,此方法将阻塞至有空间可用为止。block控制阻塞行为,默认为True。如果设置为False,将引发Queue.Empty异常(定义在Queue库模块中)。timeout指定在阻塞模式中等待可用空间的时间长短。超时后将引发Queue.Full异常。 8 9 q.qsize() #返回队列中目前项目的正确数量。此函数的结果并不可靠,因为在返回结果和在稍后程序中使用结果之间,队列中可能添加或删除了项目。在某些系统上,此方法可能引发NotImplementedError异常。 10 11 q.empty() #如果调用此方法时 q为空,返回True。如果其他进程或线程正在往队列中添加项目,结果是不可靠的。也就是说,在返回和使用结果之间,队列中可能已经加入新的项目。 12 13 .full() #如果q已满,返回为True. 由于线程的存在,结果也可能是不可靠的(参考q.empty()方法)。。 14 15 q.close() #关闭队列,防止队列中加入更多数据。调用此方法时,后台线程将继续写入那些已入队列但尚未写入的数据,但将在此方法完成时马上关闭。如果q被垃圾收集,将自动调用此方法。关闭队列不会在队列使用者中生成任何类型的数据结束信号或异常。例如,如果某个使用者正被阻塞在get()操作上,关闭生产者中的队列不会导致get()方法返回错误。 16 17 q.cancel_join_thread() #不会再进程退出时自动连接后台线程。这可以防止join_thread()方法阻塞。 18 19 q.join_thread() #连接队列的后台线程。此方法用于在调用q.close()方法后,等待所有队列项被消耗。默认情况下,此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread()方法可以禁止这种行为。
1 from multiprocessing import Queue 2 3 q=Queue(3) #创建一个队列,队列长度为3 4 5 q.put(3) # 往队列中添加数据 6 q.put(2) 7 q.put(1) 8 # q.put(4) # 如果队列已经满了,程序就会停在这里,等待数据被别人取走,再将数据放入队列。 9 # 如果队列中的数据一直不被取走,程序就会永远停在这里。 10 try: 11 q.put_nowait(4) # 可以使用put_nowait,如果队列满了不会阻塞,但是会因为队列满了而报错。 12 13 except: 14 print("队列已经满了") 15 16 # 因此我们放入数据之前,可以先看一下队列的状态,如果已经满了,就不继续put了。 17 print(q.full()) # 查看是否满了,满了返回True,不满返回False 18 19 print(q.get()) #取数据 20 print(q.get()) 21 print(q.get()) 22 # print(q.get()) # 同put方法一样,如果队列已经空了,那么继续取就会出现阻塞 23 24 try: 25 q.get_nowait() # 可以使用get_nowait,如果队列满了不会阻塞,但是会因为没有取到值而报错。 26 except: # 因此我们可以用一个try语句来处理这个错误,这样程序不会一直阻塞下去。 27 print("队列已经空了") 28 29 print(q.empty()) # 空了
8. 队列实现进程的通信