进程的创建: 1. 系统初始化(查看进程linux中用ps命令,windows中用任务管理器,前台进程负责与用户交互,后台运行的进程与用户无关, 运行在后台并且只在需要时才唤醒的进程,称为守护进程,如电子邮件、web页面、新闻、打印) 2. 一个进程在运行过程中开启了子进程(如nginx开启多进程,os.fork,subprocess.Popen等)(主要讲这个) 3. 用户的交互式请求,而创建一个新进程(如用户双击暴风影音)
无论哪一种,新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的: 1. 在UNIX中该系统调用是:fork,fork会创建一个与父进程一模一样的副本,二者有相同的存储映像、同样的环境字符串和同样的打开文件(在shell解释器进程中,执行一个命令就会创建一个子进程) 2. 在windows中该系统调用是:CreateProcess,CreateProcess既处理进程的创建,也负责把正确的程序装入新进程。
进程的状态 tail -f access.log |grep '404'
执行程序tail,开启一个子进程,执行程序grep,开启另外一个子进程,两个进程之间基于管道'|'通讯,将tail的结果作为grep的输入。
进程grep在等待输入(即I/O)时的状态称为阻塞,此时grep命令都无法运行
其实在两种情况下会导致一个进程在逻辑上不能运行,
1. 进程挂起是自身原因,遇到I/O阻塞,便要让出CPU让其他进程去执行,这样保证CPU一直在工作
2. 与进程无关,是操作系统层面,可能会因为一个进程占用时间过多,或者优先级等原因,而调用其他的进程去使用CPU。 一个进程有三个状态 运行——>阻塞——>就绪-->运行(阻塞这个状态可以一直保持) 1.运行到阻塞 进程为等待输入而阻塞 2.运行到就绪 调度程序选择另一个进程 3.就绪到运行 调度程序选择了这个进程 4.阻塞到就绪 出现有效输入 https://images2017.cnblogs.com/blog/1036857/201712/1036857-20171229144413132-1909478252.png 进程并发的实现(了解) 进程并发的实现在于,硬件中断一个正在运行的进程,把此时进程运行的所有状态保存下来,为此,操作系统维护一张表格,即进程表(process table), 每个进程占用一个进程表项(这些表项也称为进程控制块) https://images2017.cnblogs.com/blog/1036857/201712/1036857-20171229144430038-509212133.png 该表存放了进程状态的重要信息:程序计数器、堆栈指针、内存分配状况、所有打开文件的状态、帐号和调度信息,以及其他在进程由运行态转为就绪态或阻塞态时, 必须保存的信息,从而保证该进程在再次启动时,就像从未被中断过一样。
开启进程的俩种方式: from threading import Thread 造线程 开启子进程的方式一:
from multiprocessing import Process 通过这个模块导入process这个类 import time
def task(name): 通过定义函数造任务 print('%s is running' %name) time.sleep(3) print('%s is done' %name)
# 在windows系统上,开启子进程的操作必须放到if __name__ == '__main__'的子代码中 if __name__ == '__main__': p=Process(target=task,args=('egon',)) #Process(target=task,kwargs={'name':'egon'}) 传参的方式 只是造了一个对象 把task放到子进程里运行 p.start() # 只是向操作系统发送了一个开启子进程的信号 print('主') 结果:再上面睡三秒的过程中下面的主就开始运行了,所以主是第一个打印出来的 主 egon is running egon is done
开启子进程的方式二: from multiprocessing import Process import time
class Myprocess(Process): 自定义一个类(继承process这个类) def __init__(self,name): super().__init__() self.name=name
def run(self): 函数名必须是run print('%s is running' %self.name) time.sleep(3) print('%s is done' %self.name)
# 在windows系统上,开启子进程的操作必须放到if __name__ == '__main__'的子代码中 if __name__ == '__main__': p=Myprocess('egon') 用自定义的类 p.start() # 只是向操作系统发送了一个开启子进程的信号 print('主') 结果: 主 egon is running egon is done
join方法: join:让主进程在原地等待,等待子进程运行完毕,不会影响子进程的执行
from multiprocessing import Process import time
def task(name,n): print('%s is running' %name) time.sleep(n) print('%s is done' %name)
if __name__ == '__main__': p1=Process(target=task,args=('子1',1)) p2=Process(target=task,args=('子2',2)) p3=Process(target=task,args=('子3',3)) # start=time.time() p1.start() p2.start() p3.start() time.sleep(5)
p3.join() #3 p1.join() p2.join() print('主',(time.time()-start)) 等到子进程运行完了,才能运行这行代码 缩略版: start=time.time() p_l=[] for i in range(1,4): p = Process(target=task, args=('子%s' %i, i)) p_l.append(p) p.start()
for p in p_l: p.join() print('主', (time.time() - start)) 结果: 子2 is running 子1 is running 子3 is running 子1 is done 子2 is done 子3 is done 主 5.097291946411133 子1 is running 子2 is running 子3 is running 子1 is done 子2 is done 子3 is done 主 3.4291961193084717
进程之间内存空间隔离 from multiprocessing import Process
n=100 def task(): global n n=0
if __name__ == '__main__': p=Process(target=task) p.start() p.join() print(n) 在子进程运行完之后,在运行主进程代码 结果: 100
进程对象其他相关的属性或方法:
from multiprocessing import Process,current_process import time
def task(): print('%s is running' %current_process().pid) time.sleep(30) print('%s is done' %current_process().pid)
if __name__ == '__main__': p=Process(target=task) p.start() print('主',current_process().pid)(p的爹是主进程) 结果: 主 9176 9616 is running
from multiprocessing import Process,current_process import time,os
def task(): print('%s is running 爹是:%s' %(os.getpid(),os.getppid())) time.sleep(30) print('%s is done 爹是:%s' %(os.getpid(),os.getppid()))
if __name__ == '__main__': p=Process(target=task) p.start() print('主:%s 主他爹:%s' %(os.getpid(),os.getppid()))(主进程他爹是pychairm)
结果: 主:8296 主他爹:7472 7944 is running 爹是:8296
2. 进程对象其他相关的属性或方法 from multiprocessing import Process,current_process import time,os
def task(): print('%s is running 爹是:%s' %(os.getpid(),os.getppid())) time.sleep(30) print('%s is done 爹是:%s' %(os.getpid(),os.getppid()))
if __name__ == '__main__': p=Process(target=task,name='子进程1') p.start() # print(p.name) p.terminate() # time.sleep(0.1) print(p.is_alive()) print('主:%s 主他爹:%s' %(os.getpid(),os.getppid())) 结果:
主:8296 主他爹:7472 7944 is running 爹是:8296 7944 is done 爹是:8296
僵尸进程与孤儿进程 一:僵尸进程(有害) 僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。详解如下
我们知道在unix/linux中,正常情况下子进程是通过父进程创建的,子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束,如果子进程一结束就立刻回收其全部资源,那么在父进程内将无法获取子进程的状态信息。
因此,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将会以父进程的身份对僵尸状态的子进程进行处理。
二:孤儿进程(无害)
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。
我们来测试一下(创建完子进程后,主进程所在的这个脚本就退出了,当父进程先于子进程结束时,子进程会被init收养,成为孤儿进程,而非僵尸进程),文件内容 看,子进程已经被pid为1的init进程接收了,所以僵尸进程在这种情况下是不存在的,存在只有孤儿进程而已,孤儿进程声明周期结束自然会被init来销毁。
三:僵尸进程危害场景:
例如有个进程,它定期的产 生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了, 因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程 退出之后的事情,则一概不闻 不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵死进程,倘若用ps命令查看的话,就会看到很多 状态为Z的进程。 严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时 ,答案就是把产生大 量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿 进 程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程 就能瞑目而去 了。 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__': p=Process(target=task,args=('egon',)) p.start() print('主') # ps aux |grep 'Z' p.join() #init所有进程的爹,回收僵尸进程 结果: 主 egon is running egon is done 积累太多僵尸进程有害,孤儿进程无害init会定时清理
守护进程: 守护进程: 本质就是一个"子进程",该"子进程"的生命周期<=被守护进程的生命周期 主进程创建守护进程
其一:守护进程会在主进程代码执行结束后就终止
其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止
from multiprocessing import Process import time
def task(name): print('老太监%s活着....' %name) time.sleep(3) print('老太监%s正常死亡....' %name)
if __name__ == '__main__': p=Process(target=task,args=('刘清政',)) p.daemon=True p.start() time.sleep(1) print('皇上:EGON正在死...') 结果: 老太监刘清政活着.... 皇上:EGON正在死... 有一个并发任务分布到子进程,在子进程运行任务,随着在主进程任务运行完,即便没运行完,子进程也没存在的必要了
互斥锁 import json import time,random from multiprocessing import Process,Lock
def search(name): with open('db.json','rt',encoding='utf-8') as f: dic=json.load(f) time.sleep(1) print('%s 查看到余票为 %s' %(name,dic['count']))
def get(name): with open('db.json','rt',encoding='utf-8') as f: dic=json.load(f) if dic['count'] > 0: dic['count'] -= 1 time.sleep(random.randint(1,3)) with open('db.json','wt',encoding='utf-8') as f: json.dump(dic,f) print('%s 购票成功' %name) else: print('%s 查看到没有票了' %name)
def task(name,mutex): search(name) #并发 mutex.acquire() get(name) #串行 mutex.release()
# with mutex: # get(name)
if __name__ == '__main__': mutex = Lock() for i in range(10): p=Process(target=task,args=('路人%s' %i,mutex)) p.start() # p.join() # join只能将进程的任务整体变成串行
db.json {“count”:1}