进程和线程
BY:14组开发周博文
欢迎各位老师和同学指导和指正!
3、子进程的创建与启动
3-1、直接实例化Process
- 在标准库模块
multiprocessing
给我们提供了Process
类对象,这个类对象用于表示进程,通过Process
类对象我们就可以在程序中手动创建并启动一个子进程。
from multiprocessing import Process
def do_sth(arg1, arg2):
pass
process = Process(target=do_sth, args=(5, 8)) # target指定函数名, arg(元组)为该函数所对应的参数
process.start()
调用
start()
时,就在程序中手动创建并启动了一个新的进程,在调用方法start()
之后,start()
会自动调用Process中定义的run()
,然后run()
会再调用target参数指定的函数do_sth()
,因此,在我们调用了start()
之后,do_sth()
会在一个新创建并启动的进程中被调用。小结:新创建并启动的进程被称为子进程,当前py文件所对应的进程被称为父进程。
**为了区分父进程和子进程,我们将父进程和子进程的进程ID和进程名称都打印出来。
import time
from multiprocessing import Process, current_process
def do_sth(arg1, arg2):
print(f'子进程启动:{current_process().pid}--{current_process().name}') # 打印子进程的进程ID和进程名称
print(f'arg1={arg1}, arg2={arg2}')
print(f'子进程结束:{current_process().pid}--{current_process().name}')
def main():
print(f'父进程启动:{current_process().pid}--{current_process().name}') # 打印当前py文件对应进程的进程ID和进程名称
process = Process(target=do_sth, args=(5, 8)) # target指定函数名, arg(元组)为该函数所对应的参数
process.start()
time.sleep(2) # 可以使子进程结束之后再结束父进程
print(f'父进程结束:{current_process().pid}--{current_process().name}')
if __name__ == '__main__':
main()
注:多进程需要使用if name == ‘main‘方式运行,否则会报错。
运行结果:
父进程启动:26048—MainProcess 子进程启动:20268—Process-1arg1=5, arg2=8 子进程结束:20268—Process-1 父进程结束:26048—MainProcess
总结:
- 标准库模块
multiprocessing
中提供了一个类对象Process
,用于表示进程。通过类对象Process
就可以在程序中手动创建和启动子进程了。使用Process
创建并启动子进程的第1种方式为:- 根据类对象
Process
创建进程实例对象; - 调用进程实例对象的
start()
启动进程。
- 根据类对象
- 调用方法
start()
后,会自动调用方法run()
,方法run()
会自动调用参数target指定的函数。这样,新创建并启动的子进程就会调用参数target所指定的函数。如下图。
- 当我们创建Process的实例对象时,其特殊方法
__init__()
除了可以传递target和args参数外,还可以传递其他参数。Process的特殊方法__init__()
的定义如下:
init(self, group=None, target=None, name=None, args=(), kwargs={})
调用特殊方法__init__()
必须要指定关键字实参,其中:
- 参数group用于指定进程实例对象所属的进程组,默认不属于任何进程组;
- 参数target用于指定被方法
run()
所调用的函数,默认没有函数被调用 - 参数name用于指定创建的进程实例对象的名称,第n个子进程的默认名称为‘Process-n’
- 参数args用于指定target接收的位置参数,用元组表示,默认不接受位置参数
参数kargs用于指定target接收的关键字参数,用字典表示,默认不接受关键字参数
3-2、继承Process
使用类对象
Process
创建并启动子进程的第2中方式为:自定义一个继承自
Process
的类对象,重新特殊方法__init__()
和run()
;- 根据自定义的类对象创建进程实例对象;
- 调用进程实例对象的方法
start()
启动进程;调用方法start()
后,会自动调用重写后的run()
方法。
# 继承Process创建子进程
from multiprocessing import Process
class MyProcess(Process):
def __init__(self):
super().__init__()
def run(self):
print('子进程启动了')
def main():
mp = MyProcess()
mp.start()
if __name__ == '__main__':
main()
运行结果:
子进程启动了
第二种方式与第一种方式比较:
相当于把第一种方式中把参数target所指定的函数的函数体转移到重写的方法run()
中,因此,在我们创建进程实例对象时,就无需再指定参数target。
- 第一种方式创建进程实例对象时指定的其他参数,在第二种方式中可以传递给重写后的特殊方法
__init__()
# 继承Process创建子进程,并传递参数
import time
from multiprocessing import Process, current_process
class MyProcess(Process):
def __init__(self, name, args):
super().__init__(name=name)
self.args = args
def run(self):
print(f'子进程启动:{current_process().pid}--{current_process().name}') # 打印子进程的进程ID和进程名称
print('子进程启动了')
print('arg1 = %d, arg2 = %d' % self.args)
print(f'子进程结束:{current_process().pid}--{current_process().name}')
def main():
print(f'父进程启动:{current_process().pid}--{current_process().name}') # 打印当前py文件对应进程的进程ID和进程名称
mp = MyProcess(name='myprocess', args=(5, 8))
mp.start()
time.sleep(2) # 可以使子进程结束之后再结束父进程
print(f'父进程结束:{current_process().pid}--{current_process().name}')
if __name__ == '__main__':
main()
运行结果:
父进程启动:11124—MainProcess 子进程启动:18736—myprocess 子进程启动了arg1 = 5, arg2 = 8 子进程结束:18736—myprocess 父进程结束:11124—MainProcess
3-3、调用函数fork()
os.fork()无法在Windows上被调用。fork()函数并不是跨平台的,而模块multiprocessing是跨平台的。
在标准库模块os中提供函数fork(),通过该函数可以将当前的进程复制一份子进程。调用fork()函数之后,父进程就和生成的子进程从调用fork()处开始分叉,这样父进程和子进程就会兵分两路,继续运行后面的程序。
import os
os.fork() # 复制当前进程,生成子进程
但是与普通函数不同的是,fork()函数会返回两次,分别在父进程和子进程内返回,它的返回值有三种情况:
返回值小于0,表示复制子进程失败;
返回值等于0,表示当前处在子进程中;
返回值大于0,表示当前处在父进程中,并且返回值就是子进程的ID。
import os
try:
pid = os.fork() # 复制当前进程,生成子进程
except OSError:
print("你的操作系统不支持调用函数fork()")
exit()
if pid < 0:
print("复制子进程失败")
elif pid == 0:
print(f"我是子进程{os.getpid()},我的父进程是{os.getppid()}") # 该行在子进程中执行
else:
print(f"我是父进程{os.getpid()},我的子进程是{pid}") # 该行在父进程中执行
运行结果:
我是父进程56997,我的子进程是56998 我是子进程56998,我的父进程是56997