Python的time.sleep(0)在linux下的行为 - 是否会导致上下文切换?

时间:2011-09-01 16:38:05

标签: python multithreading

这种模式出现了很多,但我找不到直接的答案。

非关键,不友好的计划可能会

while(True):
    # do some work

使用其他技术和平台,如果你想允许这个程序运行热(尽可能多地使用CPU周期)但是要礼貌 - 允许其他热门程序有效地减慢我的速度,你经常写:

while(True):
    #do some work
    time.sleep(0)

我已经阅读了有关后一种方法是否会在python上运行的相互矛盾的信息,在Linux机器上运行。它是否导致上下文切换,导致我上面提到的行为?

编辑:为了什么值得,我们尝试在Apple OSX中进行一些小实验(没有方便的linux盒子)。这个盒子有4个内核和超线程,所以我们只用一个

来编写8个程序
while(True):
    i += 1

正如预期的那样,活动监视器将8个进程中的每个进程显示为占用超过95%的CPU(显然有4个内核和超线程,总共获得800%)。然后,我们制定了第九个这样的计划。现在所有9人都跑了85%左右。现在杀死第九个人并用

启动一个程序
while(True):
    i += 1
    time.sleep(0)

我希望这个过程使用接近0%而另外8个将运行95%。但相反,所有九个人都跑了85%左右。所以在Apple OSX上,sleep(0)似乎没有效果。

3 个答案:

答案 0 :(得分:21)

我从来没有想过这个,所以我写了这个剧本:

import time

while True:
    print "loop"
    time.sleep(0.5)

就像测试一样。使用strace -o isacontextswitch.strace -s512 python test.py运行它会在循环中显示此输出:

write(1, "loop\n", 5)                   = 5
select(0, NULL, NULL, NULL, {0, 500000}) = 0 (Timeout)
write(1, "loop\n", 5)                   = 5
select(0, NULL, NULL, NULL, {0, 500000}) = 0 (Timeout)
write(1, "loop\n", 5)                   = 5
select(0, NULL, NULL, NULL, {0, 500000}) = 0 (Timeout)
write(1, "loop\n", 5)                   = 5
select(0, NULL, NULL, NULL, {0, 500000}) = 0 (Timeout)
write(1, "loop\n", 5)  

select()是一个系统调用,所以是的,你是上下文切换(从技术上讲,当你改为内核空间时,实际上并不需要上下文切换,但是如果你有其他进程正在运行,你说的是什么除非您准备好读取文件描述符的数据,否则其他进程可以运行到内核中,以便执行此操作。有趣的是,延迟是在stdin上选择。这允许python在他们希望的情况下中断你对ctrl+c输入等事件的输入,而不必等待代码超时 - 我认为它非常整洁。

我应该注意,同样适用于time.sleep(0),但传入的时间参数是{0,0}。并且旋转锁定对于除了非常短的延迟之外的任何事情都不是很理想 - multiprocessingthreads提供了等待事件对象的能力。

编辑:所以我看看确切知道linux的用途。 do_selectfs\select.c)中的实施进行了此检查:

if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
    wait = NULL;
timed_out = 1;
}

if (end_time && !timed_out)
    slack = select_estimate_accuracy(end_time);

换句话说,如果提供了结束时间且两个参数均为零(!0 = 1且在C中计算为真),则等待设置为NULL并且选择被视为超时。但是,这并不意味着该功能会返回给您;它遍历您拥有的所有文件描述符并调用cond_resched,从而可能允许另一个进程运行。换句话说,发生的事情完全取决于调度程序;如果您的进程与其他进程相比占用CPU时间,则可能会发生上下文切换。如果没有,您所在的任务(内核do_select函数)可能会一直持续到完成。

然而,我会重新尝试,对其他进程更好的最佳方法通常是使用除自旋锁之外的其他机制。

答案 1 :(得分:11)

我认为你已经从@Ninefingers得到了答案,但在这个答案中我们将尝试深入研究python源代码。

首先,py time模块在​​C中实现,要查看time.sleep函数实现,您可以查看Modules/timemodule.c。正如您所看到的(并且没有获取所有平台特定的详细信息),此函数会将调用委托给floatsleep函数。

现在floatsleep旨在在不同的平台上工作,但是只要有可能,行为就会被设计为类似的,但是因为我们只对类似unix的平台感兴趣,所以我们应该检查that part only

...
Py_BEGIN_ALLOW_THREADS
sleep((int)secs);
Py_END_ALLOW_THREADS

您可以看到floatsleep正在呼叫C睡眠并且来自sleep man page

  

sleep()函数将导致调用线程被挂起   从执行到指定的实时秒数   通过参数秒已经过去或......

但是等一下我们没忘记GIL吗?

这就是Py_BEGIN_ALLOW_THREADSPy_END_ALLOW_THREADS宏出现的地方(如果您对这两个宏的定义感兴趣,请查看Include/ceval.h),上面的C代码可以使用这两个宏:

Save the thread state in a local variable.
Release the global interpreter lock.
... Do some blocking I/O operation ... (call sleep in our case)
Reacquire the global interpreter lock.
Restore the thread state from the local variable.

the c-api doc中可以找到有关这两个宏的更多信息。

希望这有用。

答案 2 :(得分:7)

您基本上是在试图篡夺OS CPU调度程序的工作。简单地调用os.nice(100)来通知调度程序您的优先级非常低,以便它可以正常工作可能会好得多。