Python中的进程
文章目录
一、多进程概念
1.什么是进程
进程:正在进行的一个过程或者说一个任务,而负责执行任务的是CPU。
2.进程和程序的区别
程序仅仅是一堆代码指令的集合而已,而进程指的是程序的运行的动态过程。
3.举例
想象以为有着一手好厨艺的科学家肖亚飞正在为自己的女儿烘焙蛋糕,他有着做生日蛋糕的食谱,厨房里有所需要的原料:面粉、鸡蛋、韭菜、蒜泥等。
在这个比喻中
做蛋糕的食谱就是程序(即用适当形式描述的算法)
计算机科学家就是处理器(CPU)
而做蛋糕的各种原料就是输入数据
进程就是厨师阅读食谱、取来各种原料以及烘焙蛋糕等一系列动作的总和
现在假设科学家的儿子哭着跑了进来,说:我的头被蜇伤了
科学家想了想,处理儿子蜇伤的任务比给女儿烘焙蛋糕的任务更重要,于是
科学家就记录下了他照着食谱做到哪儿了(保存进程的当前状态),然后拿出一本急救手册,按照其中的指示处理蜇伤。这里,我们看到处理机从一个进程(做蛋糕)切换到另一个高优先级(实施医疗救治),每个进程拥有各自的程序(食谱和急救手册)。当蜇伤处理完之后,这位科学家又回来做蛋糕,从他离开时的那一步继续做下去。
需要强调的是:同一个程序执行两次,那也是两个进程,比如打开暴风影音,虽然都是一个软件,但是一个可以播放走火,一个可以播放爱国者
4.并发和并行
无论是并发还是并行,在用户看来都是‘同时’运行的,不管是进程还是线程,都只是一个任务而已,真实干活的是CPU,CPU做这些任务,而一个CPU同一时刻只能执行一个任务
。
并发:是伪并行,即看起来是同时运行,实际上(单核下)是CPU快速交替执行,让我们看到起来一次执行多个任务。
并行:同时运行,只有具备多个CPU才能实现并行
二、进程的创建
1.创建形式
对于通用系统(跑很多应用程序),需要有系统运行过程中创建或者撤销进程的能力,主要分为4种形式创建新的进程:
- 1.系统初始化(查看进程linux中用ps命令,windows中要用任务管理器,前台进程负责与用户交互,后台运行的程序与用户无关,运行在后台并且只在需要时被唤醒的进程,称为守护进程,如:电子邮件、web界面、打印)
- 2.一个进程在运行过程中开启了子进程(如:nginx开启多进程,os.fork,subprocess.Popen等)
- 3.用户的交互式请求,而创建了一个新的进程(如:用户双击暴风影音)
- 4.一个批处理作业的初始化(只在大型机的批处理系统中应用) 无论哪一种,新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的
2.创建方法
1.在UNIX系统中,该系统调用的是fork,fork会创建一个与父进程一模一样的副本,二者拥有相同的存储映像、同样的环境字符串和同样的打开文件
(在shell解释器进程中,执行一个命令就会创建一个子进程)
2.在windows中该系统调用的是CreateProcess,CreateProcess既处理进程的创建,也负责把正确的程序装入新进程
关于创建子进程,unix和windows的相同点和不同点
相同点:进程创建后,父进程和子进程有各自不同的地址空间
(多道技术要求物理层面实现进程之间内存的隔离),任何一个进程的在其地址空间中的修改都不会影响到另外一个进程
不同点:在UNIX中,子进程的初始地址空间是父进程的一个副本,提示:子进程和父进程是可以有只读的共享内存区的。但是在windows中,从一开始父进程和子进程的地址空间就是不同的
。
3.进程的状态
- 运行态:应用程序正在被CPU执行中
- 阻塞态:当前进程突然要做I/O操作,然后CPU去执行其他的程序
- 就绪态:时刻准备着能够被执行
4.开启进程的两种方式
开启进程的第一种方式
from multiprocessing import Process
import time
def task(name):
print('%s is running'%name)
time.sleep(2)
print('%s is done'%name)
if __name__ == '__main__': # windows下开启进程的指令需要放在main下面
p = Process(target=task,args=('子进程1',)) # target代表去执行一个任务,如果是加括号的话相当于立马就执行了
p.start()
print('主进程')
# 运行结果如下:
主进程
子进程1 is running
子进程1 is done
开启进程的第二种方式
from multiprocessing import Process
import time
class MyProcess(Process): # 定制自己的方法
def __init__(self,name):
super(MyProcess, self).__init__() # 重写父类方法
self.name = name
def run(self): # 这里一定要用run
print('%s is running'%self.name)
time.sleep(2)
print('%s is done'%self.name)
if __name__ == '__main__':
# 实例化4个对象
p1 = MyProcess('子进程1')
p2 = MyProcess('子进程2')
p1.start() # 会自动调用run方法
p2.start()
print('主进程') # 首先第一步肯定是先打印出这句话
# 运行结果为:
主进程
子进程2 is running
子进程1 is running
子进程2 is done
子进程1 is done
我们看到子进程2先运行了,那么程序应该是从上到下执行,先执行子进程1然后再执行子进程2才对呀,这个我们是不可以控制进程间到底谁先执行谁后执行的,因为启动的速度太快了,类似与抢占式执行
。创建子进程的时候,会把父进程/主进程的数据复制一份作为子进程的初始数据,但进程之间的数据是共享的还是隔离的呢?让我们来证明一下:
from multiprocessing import Process
import time
num = 100 # 定义一个全局变量,是属于主进程的
def task():
global num
num = 10
print('子进程中n的值为:',num)
if __name__ == '__main__':
p1 = Process(target=task)
p1.start()
print('主进程中N的值为:',num)
# 运行结果如下:
主进程中N的值为: 100
子进程中n的值为: 10
那么经过这一段代码就可以证明,进程之间的数据是不共享的
5.查看进程的PID
让我们来看下子进程的PID,在windosw下举例:
from multiprocessing import Process
import os
import time
def task():
print('%s is running,parent id is <%s>.'%(os.getpid(),os.getppid())) # 子进程ID,父进程ID
time.sleep(3)
print('%s is done,parent id is <%s>.'%(os.getpid(),os.getppid()))
if __name__ == '__main__':
p = Process(target=task,)
p.start()
print('主进程',os.getpid(),os.getppid()) # 主进程的ID,主进程的父亲ID
# 那么运行结果是这样的
主进程 5308 8332
10408 is running,parent id is <5308>.
10408 is done,parent id is <5308>.
我们之前说过了,当一个主进程中开启子进程,那么子进程会去拷贝父进程中的数据作为子进程的原始数据,so,主进程的ID是10408,父进程的ID是5308。
那么8332是个什么鬼,我给你看个东西你就懂了
C:\Users\xiaoyafei>tasklist | findstr pycharm
pycharm64.exe 8332 Console 3 1,108,664 K
看到了吗?是pycharm的进程号。
三、僵尸进程和孤儿进程
1.僵尸进程:
就是在主进程开启了一个子进程后,无论什么时候都可以去查看子进程的状态,即使子进程死掉了,也要为主进程保留子进程状态信息,当父进程没有查看回收子进程的推出状态时就会产生僵尸进程,僵尸进程是有害的,因为一个进程死掉后,它的PID不会立马消除,如果僵尸进程多了,PID还被占用着,如果操作系统再开启新的进程的话可能就起不来,在父进程一直不死的情况下是有害的
。
2、孤儿进程:
就是子进程还没有执行完,主进程就已经死掉的了,但是子进程是无害的,此时子进程的PID由init进程去回收。
四、Process对象的其他属性和方法
1.join()方法,主进程等到子进程完成之后再去执行
在主进程运行过程中,如果想要并发的执行任务,我们可以开启子进程,此时主进程的任务和子进程的任务分两种情况:
- 1.
在主进程的任务与子进程的任务彼此独立的情况下,主进程的任务先执行完毕后,主进程还需要等待子进程执行完毕,然后统一回收资源
。 - 2.
如果主进程的任务在执行到某一阶段后,需要等待子进程执行完毕后才能继续执行,就需要有一种机制能够让主进程检测子进程进程是否运行完毕,在子进程执行完毕后才继续执行,否则一直在原地阻塞,这就是join方法的作用。
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self,name):
super().__init__()
self.name = name
def run(self):
print('%s is running.'%self.name)
time.sleep(2)
print('%s is done.'%self.name)
if __name__ == '__main__': # windows系统需要在main下开启进程
p = MyProcess('子进程') # 实例化子进程
p.start() # 给操作系统发送信号,把父进程的数据拷贝给子进程作为初始数据
p.join() # join方法,等到子进程完成之后,才会执行主进程
print('主进程')
# 运行结果如下:
子进程 is running.
子进程 is done.
主进程
有的人可能会问了,有了join()方法的话,程序不就变成串行了吗?在这里解释一下:
进程只要start就会在开始运行了,所以p.start()时,系统中已经有了1个并发的进程了,而我们p.join()是在等p结束,没错p只要不结束主线程就不会执行,这也是问题的关键,join是让主线程在等,而p或者以后的p1/p2/p3仍然是并发执行的,等到p.join()结束,可能p1/p2/p3早都结束了,这样的话,p1/p2/p3就忽略了检测,无需等待。
所以不管有多少个join()方法,需要等到的时候仍然是耗费时间最长的那个进程运行的时间。
2、terminate和is_alive方法
其中is_alive()是检测进程是否存活,而terminate方法是用来关闭进程的,当然不会立马关闭
# is_alive()检测进程是否存活
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__':
p1 = MyProcess('子进程1')
p1.start()
print('子进程进程是否存活:',p1.is_alive())
p1.join()
print('主进程')
print('第二次检测是否存活:',p1.is_alive())
# 运行结果为
子进程进程是否存活: True
子进程1 is running
子进程1 is done
主进程
第二次检测是否存活: False
这是由于主进程完成了任务,所以子进程就跟着主进程一起死掉了。
# terminate杀死进程,不会立马杀死掉
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__':
p1 = MyProcess('子进程1')
p1.start()
p1.terminate() # 杀死子进程1
print('第一次检测子进程进程是否存活:',p1.is_alive())
p1.join()
print('主进程')
print('第二次检测是否存活:',p1.is_alive())
运行结果为:
第一次检测子进程进程是否存活: True
主进程
第二次检测是否存活: False
在p1子进程刚刚把信号传递给操作系统之后,就利用了terminate方法杀死了子进程1,但是不是立马杀死,所以此时的p1子进程还是属于存活状态,等到打印完'主进程'之后,p1就跟随着主进程死掉了
,所以此时子进程存活状态为False。
3.name和pid方法
其中,查看当前进程PID为os.getpid()方法,os.getppid()为查看父进程的PID方法pid方法刚刚已经说过了,所以就不举例了
# name方法
from multiprocessing import Process
import time
def task(name):
print('%s is running'%name)
time.sleep(2)
print('%s is done'%name)
if __name__ == '__main__':
p1 = Process(target=task,args=('xiao',))
p1.start()
p1.join()
print(p1.name) # 打印子进程的进程名
print('主进程')
# 运行结果为:
xiao is running
xiao is done
Process-1 # 子进程的进程名,默认的,可以修改
主进程
如果想要修改进程名,只需要在实例化的时候添加name属性就可以了,具体操作为:
p1 = Process(target=task,args=(‘xiao’,),name=‘子进程1’)
五、守护进程
主进程设置进程,然后将该进程设置成自己的守护进程。
关于守护进程需要强调两点:
1.守护进程会跟随者主进程代码执行结束后终止
2.守护进程内无法再开启子进程,否则会抛出异常
如果我们有两个任务需要并发执行,那么我们需要开一个主进程和一个子进程去执行,如果子进程的任务在主进程执行完成后没有存在的必要了,那么该子进程应该在开启前就被设置成守护进程。主进程代码运行结束,守护进程随之终止
。
创建守护进程步骤
目的:使子进程不会拥有控制终端,即不要继承父进程的进程组id和会话组id,也就是使子进程成为进程组组长和会话组组长
1.创建子进程。fork产生子进程,由于有父进程,所以该子进程不会是进程组组长和会话期组长
2.脱离控制终端。通过setid方法,使子进程成为 新的会话期 组长,由于该会话期只有一个进程,所以该子进程也是进程组组长。这是改会话期组长是没有可控制终端的
3.禁止进程重新打开控制终端 。现在,进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端:
4.关闭打开的文件描述符
5.改变当前工作目录
6.重设文件创建掩码
7.从子进程中fork另一个子进程,该子进程不是进程组组长,也不是会话期组长,是真正的守护进程
#encoding=utf-8
import os
import sys
from time import sleep
try:
pid = os.fork()
print '完成第一次fork'
sleep(10) #sleep1
if pid > 0: sys.exit(0) # Exit first parent.
os.setsid()
print '执行setsid,使子进程成为会话期组长'
sleep(10) #sleep2
os.chdir('/')
os.umask(022) #设置当前权限掩码,同时返回先前的权限掩码。
print '完成修改工作目录和权限掩码'
sleep(10) #sleep3
pid = os.fork()
print '完成第二次fork'
sleep(10) #sleep4
if pid > 0: sys.exit(0) # Exit first parent.
print '守护进程启动完成'
sleep(10) #sleep5
except OSError, e:
sys.stderr.write("fork failed: (%d) %s\n" % (e.errno, e.strerror))
sys.exit(1)
while 1:
sleep(1)
#!/usr/bin/env python
#coding: utf-8
#pythonlinux的守护进程
import sys
import os
import time
import string
import ctypes
import datetime
from logger import *
logyyx = Logger('tsl.log', logging.ERROR, logging.DEBUG)
class Daemon:
def __init__(self, findCmd, runCmd, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
self.findCmd = findCmd
self.runCmd = runCmd
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
#self.logger = logging.getLogger()
'''
def LoggerInit(self):
logfile = '/home/***/log/tsl.log'
hdlr=logging.FileHandler(logfile)
formatter = logging.Formatter('\n%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s\n%(message)s')
hdlr.setFormatter(formatter)
self.logger.addHandler(hdlr)
self.logger.setLevel(logging.NOTSET)
return
'''
def daemonize(self):
try:
#第一次fork,生成子进程,脱离父进程
if os.fork() > 0:
raise SystemExit(0) #退出主进程
except OSError as e:
logyyx.error("fork #1 failed:\n")
#sys.exit(1)
raise RuntimeError('fork #1 faild: {0} ({1})\n'.format(e.errno, e.strerror))
os.setsid() #设置新的会话连接
os.umask(0) #重新设置文件创建权限
try:
#第二次fork,禁止进程打开终端
if os.fork() > 0:
raise SystemExit(0)
except OSError as e:
logyyx.error("fork #2 failed:\n")
#sys.exit(1)
raise RuntimeError('fork #2 faild: {0} ({1})\n'.format(e.errno, e.strerror))
os.chdir("/") # 修改工作目录
# Flush I/O buffers
sys.stdout.flush()
sys.stderr.flush()
# Replace file descriptors for stdin, stdout, and stderr
with open(self.stdin, 'rb', 0) as f:
os.dup2(f.fileno(), sys.stdin.fileno())
with open(self.stdout, 'ab', 0) as f:
os.dup2(f.fileno(), sys.stdout.fileno())
with open(self.stderr, 'ab', 0) as f:
os.dup2(f.fileno(), sys.stderr.fileno())
return
def start(self):
#检查pid文件是否存在以探测是否存在进程
esb = os.popen(self.findCmd).read().strip()
if not (esb == '0'):
print"the deamon is already running!!!"
return
else:
#启动监控
self.daemonize()
self.run()
def run(self):
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
while True:
try:
esb = os.popen(self.findCmd).read().strip()
if (esb == '0'):
logyyx.info("deamon on %s" %now)
os.system(self.runCmd)
except:
pass
time.sleep(10)
def KillPid(self,name):
ps_str = 'ps aux |grep '+name+' | grep -v grep'
x= os.popen(ps_str).read()
if x:
proc = x.split('\n')
for line in proc:
print line
try:
proc_id = line.split()[1]
os.system('kill -9 %s' % proc_id)
except:
pass
else:
return
def checkpid(self, name):
findCmd='ps -fe |grep '+name+' | grep -v grep | wc -l'
esb = os.popen(findCmd).read().strip()
if not (esb == '0'):
#杀进程
try:
self.KillPid(name)
except:
print"kill %s failed!!!" % name
logyyx.error("the deamon %s kill failed" % name)
return
return
def stop(self):
self.checkpid('main.py')
self.checkpid('deamon.py')
return
def restart(self):
self.stop()
self.start()
if __name__ == "__main__":
findCmd = 'ps -fe |grep main.py | grep -v grep | wc -l'
runCmd = 'python /home/***/main.py'
LOG = './tsl.log'
daemon = Daemon(findCmd, runCmd, stdout=LOG, stderr=LOG)
#daemon.start()
if len(sys.argv) != 2:
print('Usage: {} [start|stop]'.format(sys.argv[0]))
raise SystemExit(1)
if 'start' == sys.argv[1]:
daemon.start()
elif 'stop' == sys.argv[1]:
daemon.stop()
elif 'restart' == sys.argv[1]:
daemon.restart()
else:
print('Unknown command {0}'.format(sys.argv[1]))
raise SystemExit(1)