进程分支
分支的想法基于程序复制:当调用分支例行程序时,操作系统会创建该程序及其内存中的进程的副本,然后开始与原有程序并行的运行该副本。
分支操作后,原来的程序副本成为父进程,而由os.fork 创建的副本称为子进程,子进程在父进程结束后还可以继续运行。
示例:fork_1.py
"分支出子进程,直到你输入“q”"
import os
def child():
"子进程"
print('Hello from child! pid ->', os.getpid())
def parent():
"父进程"
while True:
new_pid_int = os.fork()
if new_pid_int == 0:
child()
os._exit(0)
else:
print('Hello from parent! parent_pid ->', os.getpid(),
'child_pid ->', new_pid_int)
if input() == 'q':
break
if __name__ == '__main__':
parent()
输出:
Hello from parent! parent_pid -> 19850 child_pid -> 19851
Hello from child! pid -> 19851
Hello from parent! parent_pid -> 19850 child_pid -> 19857
Hello from child! pid -> 19857
Hello from parent! parent_pid -> 19850 child_pid -> 19858
Hello from child! pid -> 19858
q
-
os.fork 函数为调用程序创建了一个副本,所以它为每份副本返回不同的值:在子进程中返回0,在父进程中返回新子进程的ID。 -
为了只在父进程下创建不同的进程,程序一般会对返回结果进行检验:比如这个脚本,只在子进程中运行child 函数。 -
child 进程函数也显示地用os._exit(0) 来退出。如果不调用它,子进程将在child 函数返回后延续(记住它只是原来进程的一个副本)。最后的结果是子进程将回到父进程的循环里,然后分支它自己的子进程。如果你删除那个退出调用再运行一遍,可能得多次输入“q”来停止程序。
父进程
子进程
os.fork()
os._exit(0)
父进程
子进程
为了更好地体现多个分支进程并行运行,让我们来做一点复杂点儿的操作:
示例:fork_count.py
"""
分支进程基本操作:本程序启用了5个副本,与原有程序并行运行;每个副本在一个标准输入/输出流上重复5次。
"""
import os
import time
def counter(count_int):
"子进程"
for i_int in range(count_int + 1):
time.sleep(1)
print('\n{pid_int} -> {i_int}'.format(pid_int=os.getpid(), i_int=i_int))
if __name__ == '__main__':
for i_int in range(5):
pid_int = os.fork()
if pid_int != 0:
print('Process {pid_int} spawned'.format(pid_int=pid_int))
else:
counter(5)
os._exit(0)
print('Main process exiting.')
输出:
Process 26409 spawned
Process 26410 spawned
Process 26411 spawned
Process 26412 spawned
Process 26413 spawned
Main process exiting.
26409 -> 0
26410 -> 0
26411 -> 0
26412 -> 0
26413 -> 0
26409 -> 1
26410 -> 1
26411 -> 1
26412 -> 1
26413 -> 1
26409 -> 2
26411 -> 2
26410 -> 2
26413 -> 2
26412 -> 2
26411 -> 3
26409 -> 3
26410 -> 3
26412 -> 3
26413 -> 3
26411 -> 4
26409 -> 4
26410 -> 4
26413 -> 4
26412 -> 4
26411 -> 5
26409 -> 5
26412 -> 5
26410 -> 5
26413 -> 5
- 所有这些进程的输出结果在同一个屏幕上显示,因为它们都共享标准输出流。从技术层面来说,分支进程得到原有进程的全局内存中内容的副本,包括所打开文件的描述符;如果某个子进程改变了某个全局对象,那它只是改变了自己的副本。
fork 和exec 的组合
在类Unix平台下,分支通常是启动独立运行的程序的基础,这些独立程序与执行fork 调用的程序截然不同。
示例:fork_exec.py
"运行程序,直到你输入“q”"
def main():
import os
parm_int = 0
while True:
parm_int += 1
pid_int = os.fork()
if not pid_int:
os.execlp('python', 'python', 'child.py', str(parm_int))
assert False, 'Error starting program'
else:
print('Child is', pid_int)
if input() == 'q':
break
if __name__ == '__main__':
main()
os.execlp 调用可以用一个全新的程序代替即执行覆盖当前进程中正在运行的程序,因此os.execlp 调用不会返回。如果调用返回,则表示发生了错误,因此我们在后面编写了一句assert 语句,以便抛出异常。
os.exec 调用格式
os.exec 共有8种变体:
os.execv(program, commandlinesequence) :基本的“v”执行形式,需要传入可执行程序的名称,以及用来运行程序的命令行参数字符串组成(即你在shell中起始程序通常输入的语句)的列表或元组。os.execl(program, cmdarg1, cmdarg2, ... cmdargN) :基本的“l”执行形式,需要传入可执行程序的名称,以及一个或多个以单个的函数参数形式传入的命令行参数,相当于运行os.execv(program, (cmdarg1, cmdarg2, ... cmdargN)) os.execlp 、os.execvp :加上字母“p”表示Python将使用你的系统搜索路径设置(即PATH)来定位可执行程序的目录。os.execle 、os.execve :加上字母“e”表示将在最后添加一个参数,这个参数是一个字典,包含将发送给程序的shell环境变量。os.execlpe 、os.execvpe :加上字母“p”、“e”表示使用搜索路径并且接受shell环境变量字典参数。
派生子程序
示例:child.py
import os
import sys
print('Hello from child!', os.getpid(), sys.argv[1])
输出:fork_exec.py
Child is 19999
Hello from child! 19999 1
Child is 20000
Hello from child! 20000 2
Child is 20006
Hello from child! 20006 3
q
😃 学完博客后,是不是有所启发呢?如果对此还有疑问,欢迎在评论区留言哦。 如果还想了解更多的信息,欢迎大佬们关注我哦,也可以查看我的个人博客网站BeacherHou
|