因为本人一向太随性了,最近突发奇想,希望能让自己的生活稍微规律一点点,于是拿代码试了不少东西。本来觉得顶着THU贵系的名号去吃交易这碗饭,有点对不起THU贵系的名号,所以在知乎上就不发任何编程相关的东西了(因为本人确实也菜,虽说干交易是因为明显自己有天分,但其实也有一小小小部分原因是因为本行卷不过也不想卷啊!),不过感觉有些比较好玩或者比较坑的东西还是值得分享一下。

这次我们来说说如何用Python程序控制其它的Python程序。起因是这样的,我最近几天在大规模爬金融数据,但是网络经常不是很好;或者是可能我代码写的哪里不太好,有些时候运行一段时间后会显著变慢。然后我就写了个父程序来检测这个程序它的运行情况,cpu占用等,然后决定干脆把控制权交给父程序,如果察觉到我那个程序开始跑的慢了,就kill掉,然后重新运行。因为爬金融数据主要属于网络密集型的任务,我本地也开了很多个线程,本地资源绰绰有余,中途kill掉重新来,也不会再下载已经下载完的数据,因此这还是符合需求的。

当然还有800种其他在一个程序里实现目标的方法,但是也是为了学习,顺便学了下怎么交互,就搞了这种多少有点别扭的爸爸管儿子的方法。

应该说这种方法最大的优势在于,原本的程序基本不需要做任何改动,不用瞎写一堆交互什么的,这样最大程度的保持原来策略代码的洁净性。

首先我们可以来个

main.py:
import subprocess,signal
import os
import time
import shlex
import sys
while True:
cmd ='python /Users/xxx/Desktop/xxx/xxx/fuzoku.py' # 命令本身
pi = subprocess.Popen(shlex.split(cmd), shell=False, stdout=subprocess.PIPE)
print(pi.pid)
for it in iter(pi.stdout.readline,b''):
time.sleep(0.1)
print(it)
if(it == b'1\n'):
os.kill(pi.pid,signal.SIGTERM)
time.sleep(0.1)
break
然后再来我们的任务程序,这里方便起见就搞了个很简单的程序:
fuzoku.py
import time
import random
import sys
while True:
time.sleep(0.1)
a = random.random()
if a>0.9:
print(1)
sys.stdout.flush() ## important
if a<0.2:
print(2)
sys.stdout.flush() ## important

我们来看看这两个程序在干什么:

对于任务程序,照常完成任务就行,只不过需要定时print一些标志,让main.py来接受,并用作判断;

对于主程序,在一个循环里,首先创建一个新的子进程,并获取它的id以便控制。这里最好使用shell=False的方法,更节省资源。然后main.py不断的以real time的方式获取fuzoku.py的输出。这里,fuzoku.py打印出来的东西将实时的被pi.stdout.readline所捕获,但是是以二进制形式的 (当然可以decode.('utf-8')),然后我们拿到fuzoku.py在控制台的输出之后,就可以为所欲为了。那么在这个例子里,如果fuzoku.py打印了数字1,那么传到main.py里就会变成b'1/n',然后我们就可以杀死这个进程,重新开一个新的进程,然后重复这个过程。这样就达到了控制的目的。(在子进程里开多线程完全不是问题.jpg)

程序写出来很简单,但是有一个大坑。在fuzoku.py中,如果想要实时的把数据传给main.py,就一定要每次打印完东西之后调用sys.stdout.flush()来刷新缓存,否则打印的东西就会一直堆到缓存里,待到下一次刷新缓存,或者进程结束后,一股脑还给main.py。这并不是我们所期望的。

运行一下看看:

可以看到在实时的打印fuzoku.py的输出。

显然每一个进程都应该以打印b'1\n'之后被kill作为结束,为什么44735没有呢?

原因当然在于我为了测试有没有成功杀死进程,在另一个控制台手动kill -9 44735了,然后进程就被kill掉了,可以看到意外的退出并没有影响到main.py的正常运行。这是因为

for it in iter(pi.stdout.readline,b''):

其实是一种变相的阻塞方式。当进程不存在了,这个for就自动消失了。

同时,实时检测kill -9 44734类似的,都会得到类似bash: kill: (44734) - No such process的输出,说明我们的main.py及时清理垃圾进程也是有效的。

以上是个很简化的版本,还有很多其他玩法,比如想要从main.py向fuzoku.py传东西,那就可以在main.py里使用pi.stdin.write("xxx"+os.linesep),子进程可以用sys.stdin.readline()来实时接收。还是蛮有意思的。当然这个时候就注意不要两边都再打印那边打印过的东西,这样显而易见,没一会缓存就爆掉了。。。