Python subprocess ThreadPool 优雅的任务管理解决方案记录

我想要实现的:类似下载任务管理程序,最多同时下载5个,当下载任务有20个时,只同时下载5个,等待正在下载的5个任务完成一个,再开始下一个新的下载任务。

起因:我有一个python程序,运行时当鼠标点击视频,就下载这个视频。我用subprocess来执行下载程序,从而实现了多进程同时下载。
但是这样有个问题,当我频繁的点击视频,前面的下载任务还没完成,后面的下载任务就被激活了,进程里会同时执行非常多的下载程序,这导致了电脑非常的卡顿,并且很多下载程序因此终止。这并不优雅。
所以我希望python程序最多运行n个(如5个)下载程序,如果已经运行5个下载程序了,我后续的点击视频的事件不会马上激活下载程序,而是等待前5个下载程序完成其中一个,然后再开始,以此类推,后续的点击都会排队等待前5个下载视频完成其中1个再开始下载。

我想到的是queue,queue只有5格长,下载完成了出列,然后在排队等待的任务加入队列,但我完全不知道怎么去模拟“排队等待”这个效果,而且我觉得queue也不完全符合我的想法,因为queue必须是早进的早出,但可能晚入列的比早入列的下载完。

在百度上搜索queue和subprocess关键字没能找到答案,国内关于subprocess的帖子似乎很少。于是我到google搜索了一下,找到了一个完全符合我需求的帖子。
链接:Stackflow:Python multiple subprocess with a pool/queue recover output as soon as one finishes and launch next job in queue?
这个帖子的提问大概是:我用python的subprocess创建了多进程,想要结合pool(池)或者queue(队列)来实现:当一个任务完成时,下一个任务加入queue(队列)并开始执行。

得到的最高投票的回答如下:

ThreadPool could be a good fit for your problem, you set the number of worker threads and add jobs, and the threads will work their way through all the tasks.

ThreadPool可以很好的符合你问题的需求,你可以设置工作线程的最大数量并添加任务,这些线程将以自己的方式完成所有任务。

该回答附带的源码如下

from multiprocessing.pool import ThreadPool
import subprocess


def work(sample):
    my_tool_subprocess = subprocess.Popen('mytool {}'.format(sample),shell=True, stdout=subprocess.PIPE)
    line = True
    while line:
        myline = my_tool_subprocess.stdout.readline()
        #here I parse stdout..


num = None  # set to the number of workers you want (it defaults to the cpu count of your machine)
tp = ThreadPool(num)
for i in all_samples:
    tp.apply_async(work, (sample,))

tp.close()
tp.join()

ThreadPool,线程池。
虽叫“线程”,但和多进程还是多线程无关,我用subprocess,就是多进程,ThreadPool就是一个任务管理员罢了,我只需要关注这些任务,和最大同时执行的任务数,剩下的,交给他就完事了~
然后我改变成了我的代码

# 伪代码

def startDownload(url):
    subprocess.Popen("start python download.py "+url, shell=True)


threadManager = ThreadPool(5)

While 点击视频:
	url = 获取点击的视频的链接()
	threadManager.apply_async(startDownload, url)

接下来我做了一个控制变量的实验。
实验条件,threadManager = ThreadPool(3),发起5个任务

多进程发起方式是否有startshell是否truedownload.py发起方式download.py是否wait()thread有无asyncthread有无close和join结果resultOftheExperiment
os.system*subprocess.Popenwait()实现目的,但输出混乱,在前三个进程未执行完时按下关闭按钮,整个程序没被关掉,前三个任务似乎被关闭,开始第四个任务和第五个任务,再按一次关闭按钮才将程序关闭
os.systemstart*subprocess.Popenwait()5个进程以以新窗口的方式打开,同时进行,关闭主进程,子进程不会被关闭,似乎已经“独立”
subprocess.Popenfalsesubprocess.Popenwait()5个进程同时运行,输出混乱,按下关闭按钮直接关闭整个程序
subprocess.Popentruesubprocess.Popenwait()5个进程同时运行,输出混乱,按下关闭按钮直接关闭整个程序
subprocess.Popenstartfalsesubprocess.Popenwait()根本无法发起下载任务,无报错,没有反应
subprocess.Popenstarttruesubprocess.Popenwait()5个进程以以新窗口的方式打开,同时进行,关闭主进程,子进程不会被关闭,似乎已经“独立”

看来要采用os.system的方式发起进程,并且不能加上start来开启新的窗口。
然后我进行了新一轮实验

多进程发起方式是否有startshell是否truedownload.py发起方式download.py是否wait()thread有无asyncthread有无close和join结果resultOftheExperiment
os.system*subprocess.Popenwait()实现目的,但输出混乱,在前三个进程未执行完时按下关闭按钮,整个程序没被关掉,前三个任务似乎被关闭,开始第四个任务和第五个任务,再按一次关闭按钮才将程序关闭
os.system*subprocess.Popenwait()只启动一个下载任务,并且监听点击视频事件被阻塞,但实际上仍在监听,在这个下载任务结束后,自动开起下一个下载任务
os.system*subprocess.Popenwait()实现目的,和【1】的反应一样。因为我的点击视频事件监听函数不会自己结束,所以我觉得不需要close和join来阻塞主进程结束

看来必须使用apply_async,如果主进程自己不会关闭,则不需要threadManager.close()和threadManager.join()来让主进程等待子进程完成任务。
然后我进行了新一轮实验

多进程发起方式shell是否truedownload.py发起方式是否startdownload.py是否wait()结果resultOftheExperiment
os.system*subprocess.Popenwait()实现目的,但输出混乱,在前三个进程未执行完时按下关闭按钮,整个程序没被关掉,前三个任务似乎被关闭,开始第四个任务和第五个任务,再按一次关闭按钮才将程序关闭
os.system*subprocess.Popen同时执行5个下载任务,输出混乱
os.system*subprocess.Popenstartwait()5个进程以以新窗口的方式打开,同时进行,关闭主进程,子进程不会被关闭,似乎已经“独立”
os.system*subprocess.Popenstart5个进程以以新窗口的方式打开,同时进行,关闭主进程,子进程不会被关闭,似乎已经“独立”
os.system*os.system*实现目的,但输出混乱,在前三个进程未执行完时按下关闭按钮,整个程序没被关掉,前三个任务似乎被关闭,开始第四个任务和第五个任务,再按一次关闭按钮才将程序关闭
os.system*os.systemstart*5个进程以以新窗口的方式打开,同时进行,关闭主进程,子进程不会被关闭,似乎已经“独立”

如果用subprocess.popen,则必须加wait阻塞,如果用os.system,则天然可以阻塞。都不能加start,加start,start成功程序即代表结束。

总结:用apply_async和os.system,并且不加start即可满足需求。

# 伪代码

def startDownload(url):
    os.system("start python download.py "+url)


threadManager = ThreadPool(5)

While 点击视频:
	url = 获取点击的视频的链接()
	threadManager.apply_async(startDownload, url)

如果主进程不用os.system而是subprocess,并且wait会发生什么呢
然后我做了如下实验

多进程发起方式是否有startshell是否true结果resultOftheExperiment
os.system*实现目的,但输出混乱,在前三个进程未执行完时按下关闭按钮,整个程序没被关掉,前三个任务似乎被关闭,开始第四个任务和第五个任务,再按一次关闭按钮才将程序关闭
subprocess.Popen+wait实现目的,但输出混乱,在前三个进程未执行完时按下关闭按钮,整个程序没被关掉,前三个任务似乎被关闭,开始第四个任务和第五个任务,再按一次关闭按钮才将程序关闭

看来加了个wait,让函数等待subprocess完成,也可以实现阻塞效果。所以我估计subprocess不加wait,“创建进程”这个工作一做完,ThreadPool就认为这个函数执行结束了,所以价格wait也能实现效果。

但是目前输出还是混乱的,之前为什么要研究start,是因为start可以实现打开新窗口并执行python指令,但是前面的实验也可以看到,加了start后这些子进程都独立了,脱离了主进程,因为关闭主进程,子进程仍在执行,所以可以推断start其实本身就是调用了系统的start,然后启动下载程序,而不是由主进程直接唤起下载程序。

我们也可以尝试subprocess+ wait阻塞+start的组合,同样发现子进程独立,5个任务同时下载的情况。

所以我们迫切需要找一种既能打开新窗口运行下载程序,又能让下载程序隶属于ThreadPool被管理。

在google上找到了这篇文章
Stackflow:subprocess.Popen in different console
最多投票给出的代码是:

from subprocess import Popen, CREATE_NEW_CONSOLE

Popen('cmd', creationflags=CREATE_NEW_CONSOLE)

input('Enter to exit from Python script...')

我加了个wait改造成我的代码

# 伪代码
from subprocess import Popen, CREATE_NEW_CONSOLE
from multiprocessing.pool import ThreadPool


def startDownload(url):
    command = "start python download.py "+url
	p = Popen(command, creationflags=CREATE_NEW_CONSOLE)
	p.wait()

threadManager = ThreadPool(5)

While 点击视频:
	url = 获取点击的视频的链接()
	threadManager.apply_async(startDownload, url)

而download.py用的是os.system或者subprocess+wait
这个代码完美的满足的我的需求。
下载任务管理,分窗口输出进度。

优雅,永不过时

所以总结一下
本次涉及到的因素有
os.system()
poepn+CREATE_NEW_CONSOLE+wait
subprocess.popen+wait
command是否含有start
首先os.system是阻塞的
subprocess.popen+wait是阻塞的
但只要这两者都加上“start”,就不阻塞了,因为他们执行start,相当于是“叫系统去开启”,而不是自己开启,他们叫完,他们事情就没了。这也是为什么start能开启新窗口。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值