在循环中使用"time.sleep"和使用"threading.Timer"的资源使用情况对比

7

第一种方法:

import threading
import time

def keepalive():
    while True:
        print 'Alive.'
        time.sleep(200)
threading.Thread(target=keepalive).start()

第二种方法:

import threading

def keepalive():
    print 'Alive.'
    threading.Timer(200, keepalive).start()

threading.Timer(200, keepalive).start()

哪种方法占用的RAM更多?在第二种方法中,线程在被激活后是否会结束?还是它会保留在内存中并启动一个新线程?(多个线程)


1
我稍微将标题泛化了一些 - 对于这种特定的“基准测试”或“性能问题”,一个好的初始答案是对其进行分析; 显示“意外结果”的分析是提问的更好起点。 - user166390
1
你试过运行它吗?你可以使用 top/Activity Monitor/ProgMan 等工具来查看内存使用和线程数,或者你可以在程序内部执行(例如 threading.active_count()),这可能比你提出一个合理的问题所需的时间更短。 - abarnert
循环和sleep()调用有什么问题?为什么每次都要启动一个新线程? - Martin James
2个回答

8

Timer会为每个启动的计时器创建一个新的线程对象,因此在创建和垃圾回收这些对象时需要更多的资源。

由于每个线程在生成另一个线程后立即退出,所以active_count保持不变,但是不断地创建和销毁新的线程会导致开销增加。我认为第一种方法明显更好。

虽然你不会看到太大的差异,但只有当间隔非常短的时候才会有区别。


这正是我一直在想的。虽然每种方法的内存使用量似乎几乎相同,但当我使用第二种方法时,我的低端Ubuntu服务器的OOM-killer总是会杀死我的程序。 - Taha

6
这里是一个测试的例子:

在第二种方法中,线程被激活后会结束吗?还是会保留在内存中并启动一个新的线程?(多个线程)

import threading

def keepalive():
    print 'Alive.'
    threading.Timer(200, keepalive).start()
    print threading.active_count()

threading.Timer(200, keepalive).start()

我还将200改为0.2,以便时间不会太长。

线程计数一直是3。

然后我做了这个:

top -pid 24767

#TH列从未改变。

因此,这就是答案:我们没有足够的信息来知道Python是否为所有计时器维护单个计时器线程,或者在计时器运行后立即结束和清理线程,但我们可以确定线程不会停留并堆积。(如果您确实想知道前者中发生了什么,可以打印线程ID。)

另一种查找方法是查看源代码。正如文档所说,“Timer是Thread的子类,因此也可以作为创建自定义线程的示例”。它是Thread的子类已经告诉您每个Timer都是一个Thread。而它“作为示例运行”这一事实意味着它应该很容易阅读。如果您点击文档中的链接源代码,您可以看到它是多么简单。大部分工作由Event完成,但它在同一个源文件中,并且几乎一样简单。实际上,它只创建了一个条件变量,在它上面等待(因此它会阻塞直到超时或通过调用cancel通知条件),然后退出。

我回答一个子问题并解释我是如何做到的,而不是回答每个子问题,因为我认为通过相同的步骤更有用。

进一步思考后,这可能不是首先需要通过优化来决定的问题:
如果您有一个简单的同步程序需要在200秒内不做任何事情,请调用阻塞式的sleep。或者更简单地,只需完成工作并退出,选择一个外部工具来安排脚本每200秒运行一次。
另一方面,如果您的程序本质上是异步的——特别是如果您已经有了线程、信号处理程序和/或事件循环——那么您绝对无法让sleep起作用。如果Timer效率太低,去PyPI或ActiveState找到一个更好的定时器,它可以让您使用单个实例和线程安排可重复的定时器(甚至多个定时器)。 (或者,如果您正在使用信号,请使用signal.alarmsetitimer,如果您正在使用事件循环,请将计时器构建到主循环中。)
我想不出任何情况下,sleepTimer都会成为严肃的竞争者。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接