如何同时启动两个功能,只等待更快的一个?

Posted

技术标签:

【中文标题】如何同时启动两个功能,只等待更快的一个?【英文标题】:How to start two functions at the same time and only wait for the faster one? 【发布时间】:2021-01-31 04:12:11 【问题描述】:

我有一个工作代码,但不确定这是正确的方法。我有两个函数,都发出一个可能需要 1 到 5 秒之间任意时间的 API 请求,但两者都旨在返回相同的输出。我想同时运行这两个,一旦更快的完成它的工作,终止另一个并丢弃它会返回的任何东西。

p1 = Process(target = search1, args=(name) )
p2 = Process(target = search2, args=(name) )

if __name__=='__main__':
    p1.start()
    p2.start()

    while p1.is_alive() and p2.is_alive():
        time.sleep(0.2)

        if not p1.is_alive():
            p2.terminate()

        if not p2.is_alive():
            p1.terminate()

如果我不等待一段时间(在这种情况下为 0.2 秒),有时如果两者花费的时间相同,则两者都会返回。我测试了很多次并且它有效,但这是正确的方法吗?这种方法是否会出现任何问题?

--- ti7 建议

在 ti7 的建议下尝试使用线程,现在它可以使用线程而不是进程。

def search1(Q_result, name):
    result = somefunction()
    Q_result.put(result)

def search2(Q_result, name):
    time.sleep(10)
    result = somefunction()
    Q_result.put(result )


import Queue as queue
import threading

Q_result = queue.Queue()  # create a Queue to hold the result(s)

if __name__=='__main__':

    t1 = threading.Thread(
        target=search1,
        args=(Q_result, name),
    )
    t1.daemon = True
    t1.start()
        

    t2 = threading.Thread(
        target=search2,
        args=(Q_result),
    )
    t2.daemon = True
    t2.start()


print(Q_result.get())

【问题讨论】:

你有很多这样的任务要做吗?因为对于数千个任务来说,启动和终止进程需要相当多的时间和资源。此外,如果它只是一个 API 请求,那么您可能可以使用与 threading 模块中的轻量级线程相同的方法。但总的来说,您的方法还不错,是解决任务的一种方法。但是从某种意义上说,您的解决方案很好,因为它可以清除所有资源,因为当进程被杀死时,所有资源都会被释放。但是如果你使用线程,那么一些资源可能会被泄露。 我还有一个想法 - 如果您必须执行许多任务、许多请求,并且您的下一个请求不依赖于先前请求的结果,那么您可以同时执行 2-3 个相同的 API 请求可以同时做不同的请求,很多是并行的。只有当其中一些请求失败时,才重试第二次。这种技术不仅可以让您提高总体性能,还可以减少 API 服务器上不必要的重量。 这是一个移动客户端的搜索功能,所以是的,这些功能甚至可以在当前高峰使用时同时运行 20-30 次。你推荐在这种情况下使用 threading 模块吗? 顺便说一句,有一些自动化工具可以自动转换 Py2->Py3。一个是Modernizer,另一个是Futurize。第二个是一件很酷的事情,因为它以这样一种方式进行转换,之后 Py2 和 Py3 都可以运行相同的脚本。此外,Futurizer 只是在脚本的开头添加了额外的导入,这些导入使您的进一步代码可以同时由 Py2 和 Py3 运行,因此您的脚本几乎没有修改但升级了。我心目中的理想解决方案。 顺便说一句,@ti7 刚刚更新了他对另一个守护进程解决方案的回答,而不是 daemon = True 构造函数参数,您可以在 Python 2 中的线程 t 实例上使用 t.daemon = True 【参考方案1】:

如果您多次提出相同的请求,您最好只做一次并联系服务的所有者以提高其性能。 (例如,它可能正在分发连接并且其中一个节点非常慢)。

正如 @Arty 所说,使用threads 将比创建进程更轻松,因此性能更高。您可以使线程成为守护进程,因此它们不需要.join()ed 即可退出(阻止程序退出,直到所有线程都完成)。

异步逻辑可能会更快一些,但推理起来可能会令人沮丧,尤其是在 Python 2 中。此外,您可能会发现,如果您使用的是 3rd-party 库,例如 Twisted's Defferred,会加载所需的库速度非常慢并会降低整体性能。

使用线程,您可能会发现获取结果并将其放入queue.Queue 很方便,它既是线程安全的,又可以block until content is available。

粗线示例

from __future__ import print_function  # print(x) over print x
import queue
import threading

# if these are the same logic, use an argument to differentiate
# otherwise you could have any number of unique functions,
# each of which makes some different request
def make_request(Q_result, request_args):
    result = "whatever logic is needed to make request A"
    Q_result.put(result)  # put the result into the Queue

list_of_different_request_args = []  # fill with whatever you need

Q_result = queue.Queue()  # create a Queue to hold the result(s)

# iterate over input args (could be list of target functions instead)
for request_args in list_of_different_request_args:
    t = threading.Thread(
        target=make_request,
        args=(Q_result, request_args),
    )
    t.daemon = True  # set via arg in Python 3
    t.start()

# get the first result, blocking until one is available
print(Q_result.get())

# program exits and discards threads

【讨论】:

嗨!我根据您的建议编辑了我的代码,现在看看这个问题。问题是它不接受守护进程作为 init 中的关键字。如果我删除它,它会运行,但较慢的线程会阻止退出。可能是什么问题? @AkosF 对不起!自从我使用 Python 2 以来已经有一分钟了;这是一个应该在启动线程之前设置的属性 - 我已经更新了我的示例!文档:docs.python.org/2/library/… 非常感谢您的帮助,现在它就像魅力一样! 嗨@ti7!希望你看到这个问题 :) 如果两个线程都因异常而失败,因此主线程被永远阻塞,我正在努力处理这种情况。知道在这种情况下如何安全地退出两个线程吗?提前非常感谢! @AkosF 请提出一个新问题并将其链接到此处,再次标记我,但Queue.get() 接受超时参数,这将允许您停止阻塞并继续退出(或您的任何默认值)喜欢)文档:docs.python.org/3/library/queue.html#queue.Queue.get

以上是关于如何同时启动两个功能,只等待更快的一个?的主要内容,如果未能解决你的问题,请参考以下文章

主程序如何通过等待而不是加入同时等待多个线程?

等待2个方法而不是1个[重复]

Python等待一个带有循环的函数

两个线程同时执行同步块

如何修改LINUX和WINDOWS双系统的启动顺序和等待时间啊?

如何告诉python使用者等待客户端启动