tl; dr:我有几个线程,一个是监听input()的线程,以保持程序在keypress上运行/退出。但是在程序中有一次我需要停止这个监听器,否则它将截取子处理程序的输入。
长版:
- 程序应下载一些数据,然后将其交给其他一些控制台程序进行处理
- 程序应该在下载完成之前运行或直到发送ENTER-keypress为止
- 在这两种情况下,下载线程都将正常结束,并且应完成外部处理
- 问题:input()函数仍在侦听并拦截子进程控制台程序的第一个输入。
import os
import subprocess
import threading
import time
def thread_do_downloads():
# does some downloads and will set the flag "flag_download_completed=True"
# eventually to signal download completed
# for this example just set the flag
global flag_download_completed
flag_download_completed = True
def do_stuff_with_downloaded_data():
# this is of course not the program I would call,
# but this example should show how the input would be intercepted
if os.name == 'nt':
parameters = ["set", "/p", "variable=Press Enter"] # for this example (Windows) call "set", this program will wait for a user input
else:
parameters = ["read", "variable"] # hope this works for linux...
p1 = subprocess.Popen(parameters, shell=True)
p1.communicate()
def listen_for_keypress():
input()
print("keypress intercepted")
def main():
dl = threading.Thread(target=thread_do_downloads)
dl.start()
kill_listener = threading.Thread(target=listen_for_keypress, daemon=True) # daemon: to not have it lingering after main thread is done
kill_listener.start()
print("Press ENTER to stop downloading.")
while True:
if not kill_listener.is_alive() or flag_download_completed:
break
time.sleep(1)
# here are some lines to make sure the download thread above completes gracefully
do_stuff_with_downloaded_data()
print("All done")
if __name__ == '__main__':
flag_download_completed = False
main()
将导致:
按ENTER键停止下载
按Enter <<停在这里直到我按下ENTER
keypress截获<<停在这里直到我按下ENTER
全部完成
答案 0 :(得分:0)
如果你可以将主线程放在控制台的顶部,也许你可以利用input()
将阻止主线程直到Enter
被按下为止的事实。一旦执行继续(因为按下Enter
),请与正在运行的线程通信,他们必须停止使用Event(另一个示例here)。如果你确实想听S.O.信号,我建议你看看signal模块(注意,一些功能可能是O.S依赖)。
import threading
import time
def thread_do_downloads(stop_activated):
# does some downloads and will set the flag "flag_download_completed=True"
# eventually to signal download completed
# for this example just set the flag
global flag_download_completed
while not stop_activated.is_set():
time.sleep(0.5)
print("ZZZZZZZ")
def do_stuff_with_downloaded_data():
print("doing stuff with downloaded data")
def main():
stop_activated = threading.Event()
dl = threading.Thread(target=thread_do_downloads, args=(stop_activated,))
dl.start()
input("Press ENTER to stop downloading.")
stop_activated.set()
print("stopping (waiting for threads to finish...)")
dl.join()
# here are some lines to make sure the download thread above completes gracefully
do_stuff_with_downloaded_data()
print("All done")
if __name__ == '__main__':
main()
编辑(根据OP的评论):
原始问题的一个复杂问题是如何将终止请求传递给子进程。因为进程不与父进程(产生进程的进程)共享内存,所以实际上只能通过实际的SO信号(或几乎只)来完成。由于这种内存隔离,父进程上设置的任何标志都不会对生成的子进程产生影响:进程间通信的唯一方法是通过操作系统信号,或通过父级和子级的文件(或类文件结构)过程“已知”并用于共享信息。此外,在父级中调用input()
会将标准输入(stdin
)绑定到该进程,这意味着默认情况下,子进程不知道父级中按下的键(您可以始终绑定{{ 1}}子进程到父进程的stdin
,但这会使代码更复杂一些)
幸运的是,Popen
的实例确实提供了向子进程发送信号的好方法:TERM信号,子进程可以捕获并且应该将其解释为“嘿你很快就会被阻止,所以清理你的东西,关闭文件等等,然后退出“和KILL信号,这些信号对子进程没有任何意义(可以“它被捕获”:它只会杀死它(在Linux中,例如KILL信号会从被杀死的进程中删除对内存的所有访问,因此任何使用内存的操作,例如寻找下一个操作都会导致错误。更多信息{ {3}})
为了证明这一点,假设我们在主程序所在的同一目录中有一个简单的stdin
文件,如下所示:
script.py >
script.py
一个需要随机时间处理的脚本,可能会很长(至少足够长时间来演示)
现在,我们可以在运行#!/usr/bin/env python
import sys
import random
import time
def main():
done = False
while not done:
time.sleep(0.5)
print("I'm busy doing things!!")
done = random.randint(0, 15) == 1
if __name__ == "__main__":
main()
sys.exit(0) # This is pretty much unnecessary, though
文件的步骤中创建一个(或多个)子进程,定期检查其状态(使用here),如果用户请求强制输出发送如果需要,script.py
发出信号,稍后再发出TERM
。
KILL