进程和线程

BY:14组开发周博文
欢迎各位老师和同学指导和指正!

3、子进程的创建与启动

3-1、直接实例化Process

  • 在标准库模块multiprocessing给我们提供了Process类对象,这个类对象用于表示进程,通过Process类对象我们就可以在程序中手动创建并启动一个子进程。


  1. from multiprocessing import Process
  2. def do_sth(arg1, arg2):
  3. pass
  4. process = Process(target=do_sth, args=(5, 8)) # target指定函数名, arg(元组)为该函数所对应的参数
  5. process.start()
  • 调用start()时,就在程序中手动创建并启动了一个新的进程,在调用方法start()之后,start()会自动调用Process中定义的run(),然后run()会再调用target参数指定的函数do_sth(),因此,在我们调用了start()之后,do_sth()会在一个新创建并启动的进程中被调用。

    小结:新创建并启动的进程被称为子进程,当前py文件所对应的进程被称为父进程。
    **

  • 为了区分父进程和子进程,我们将父进程和子进程的进程ID和进程名称都打印出来。


  1. import time
  2. from multiprocessing import Process, current_process
  3. def do_sth(arg1, arg2):
  4. print(f'子进程启动:{current_process().pid}--{current_process().name}') # 打印子进程的进程ID和进程名称
  5. print(f'arg1={arg1}, arg2={arg2}')
  6. print(f'子进程结束:{current_process().pid}--{current_process().name}')
  7. def main():
  8. print(f'父进程启动:{current_process().pid}--{current_process().name}') # 打印当前py文件对应进程的进程ID和进程名称
  9. process = Process(target=do_sth, args=(5, 8)) # target指定函数名, arg(元组)为该函数所对应的参数
  10. process.start()
  11. time.sleep(2) # 可以使子进程结束之后再结束父进程
  12. print(f'父进程结束:{current_process().pid}--{current_process().name}')
  13. if __name__ == '__main__':
  14. main()

注:多进程需要使用if name == ‘main方式运行,否则会报错。
运行结果:

父进程启动:26048—MainProcess 子进程启动:20268—Process-1arg1=5, arg2=8 子进程结束:20268—Process-1 父进程结束:26048—MainProcess

总结:

  • 标准库模块multiprocessing中提供了一个类对象Process,用于表示进程。通过类对象Process就可以在程序中手动创建和启动子进程了。使用Process创建并启动子进程的第1种方式为:
    1. 根据类对象Process创建进程实例对象;
    2. 调用进程实例对象的start()启动进程。


  • 调用方法start()后,会自动调用方法run(),方法run()会自动调用参数target指定的函数。这样,新创建并启动的子进程就会调用参数target所指定的函数。如下图。

03子进程的创建与启动 - 图1

  • 当我们创建Process的实例对象时,其特殊方法__init__()除了可以传递target和args参数外,还可以传递其他参数。Process的特殊方法__init__()的定义如下:

init(self, group=None, target=None, name=None, args=(), kwargs={})
调用特殊方法__init__()必须要指定关键字实参,其中:

  1. 参数group用于指定进程实例对象所属的进程组,默认不属于任何进程组;
  2. 参数target用于指定被方法run()所调用的函数,默认没有函数被调用
  3. 参数name用于指定创建的进程实例对象的名称,第n个子进程的默认名称为‘Process-n’
  4. 参数args用于指定target接收的位置参数,用元组表示,默认不接受位置参数
  5. 参数kargs用于指定target接收的关键字参数,用字典表示,默认不接受关键字参数

    3-2、继承Process

    使用类对象Process创建并启动子进程的第2中方式为:

  6. 自定义一个继承自Process的类对象,重新特殊方法__init__()run();

  7. 根据自定义的类对象创建进程实例对象;
  8. 调用进程实例对象的方法start()启动进程;调用方法start()后,会自动调用重写后的run() 方法。


  1. # 继承Process创建子进程
  2. from multiprocessing import Process
  3. class MyProcess(Process):
  4. def __init__(self):
  5. super().__init__()
  6. def run(self):
  7. print('子进程启动了')
  8. def main():
  9. mp = MyProcess()
  10. mp.start()
  11. if __name__ == '__main__':
  12. main()

运行结果:

子进程启动了

第二种方式与第一种方式比较:
相当于把第一种方式中把参数target所指定的函数的函数体转移到重写的方法run()中,因此,在我们创建进程实例对象时,就无需再指定参数target。
03子进程的创建与启动 - 图2

  • 第一种方式创建进程实例对象时指定的其他参数,在第二种方式中可以传递给重写后的特殊方法__init__()
  1. # 继承Process创建子进程,并传递参数
  2. import time
  3. from multiprocessing import Process, current_process
  4. class MyProcess(Process):
  5. def __init__(self, name, args):
  6. super().__init__(name=name)
  7. self.args = args
  8. def run(self):
  9. print(f'子进程启动:{current_process().pid}--{current_process().name}') # 打印子进程的进程ID和进程名称
  10. print('子进程启动了')
  11. print('arg1 = %d, arg2 = %d' % self.args)
  12. print(f'子进程结束:{current_process().pid}--{current_process().name}')
  13. def main():
  14. print(f'父进程启动:{current_process().pid}--{current_process().name}') # 打印当前py文件对应进程的进程ID和进程名称
  15. mp = MyProcess(name='myprocess', args=(5, 8))
  16. mp.start()
  17. time.sleep(2) # 可以使子进程结束之后再结束父进程
  18. print(f'父进程结束:{current_process().pid}--{current_process().name}')
  19. if __name__ == '__main__':
  20. 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()处开始分叉,这样父进程和子进程就会兵分两路,继续运行后面的程序。

  1. import os
  2. os.fork() # 复制当前进程,生成子进程
  3. 但是与普通函数不同的是,fork()函数会返回两次,分别在父进程和子进程内返回,它的返回值有三种情况:
  4. 返回值小于0,表示复制子进程失败;
  5. 返回值等于0,表示当前处在子进程中;
  6. 返回值大于0,表示当前处在父进程中,并且返回值就是子进程的ID
  7. import os
  8. try:
  9. pid = os.fork() # 复制当前进程,生成子进程
  10. except OSError:
  11. print("你的操作系统不支持调用函数fork()")
  12. exit()
  13. if pid < 0:
  14. print("复制子进程失败")
  15. elif pid == 0:
  16. print(f"我是子进程{os.getpid()},我的父进程是{os.getppid()}") # 该行在子进程中执行
  17. else:
  18. print(f"我是父进程{os.getpid()},我的子进程是{pid}") # 该行在父进程中执行

运行结果:

我是父进程56997,我的子进程是56998 我是子进程56998,我的父进程是56997