Python编程 | 系统编程 | 并行系统工具 | 进程分支

进程分支

分支的想法基于程序复制:当调用分支例行程序时,操作系统会创建该程序及其内存中的进程的副本,然后开始与原有程序并行的运行该副本。

分支操作后,原来的程序副本成为父进程,而由os.fork创建的副本称为子进程,子进程在父进程结束后还可以继续运行

示例:fork_1.py

#!/usr/bin/env python

"分支出子进程,直到你输入“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”来停止程序。

为了更好地体现多个分支进程并行运行,让我们来做一点复杂点儿的操作:

示例:fork_count.py

#!/usr/bin/env python


"""
分支进程基本操作:本程序启用了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

  • 所有这些进程的输出结果在同一个屏幕上显示,因为它们都共享标准输出流。从技术层面来说,分支进程得到原有进程的全局内存中内容的副本,包括所打开文件的描述符;如果某个子进程改变了某个全局对象,那它只是改变了自己的副本。

forkexec的组合

在类Unix平台下,分支通常是启动独立运行的程序的基础,这些独立程序与执行fork调用的程序截然不同。

示例:fork_exec.py

#!/usr/bin/env python


"运行程序,直到你输入“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'							# 不应该返回,因为原程序已被覆盖为child.py
		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.execlpos.execvp:加上字母“p”表示Python将使用你的系统搜索路径设置(即PATH)来定位可执行程序的目录。
  • os.execleos.execve:加上字母“e”表示将在最后添加一个参数,这个参数是一个字典,包含将发送给程序的shell环境变量。
  • os.execlpeos.execvpe:加上字母“p”、“e”表示使用搜索路径并且接受shell环境变量字典参数。
派生子程序

示例:child.py

#!/usr/bin/env python


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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

RizzoHou

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值