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

Mat*_*und 29 python multithreading

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

一个非关键的,不友好的计划可能会这样做

while(True):
    # do some work
Run Code Online (Sandbox Code Playgroud)

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

while(True):
    #do some work
    time.sleep(0)
Run Code Online (Sandbox Code Playgroud)

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

编辑:为了什么值得,我们在Apple OSX中尝试了一个小实验(没有方便的linux盒子).这个盒子有4个核心加上超线程,所以我们只用a来编写8个程序

while(True):
    i += 1
Run Code Online (Sandbox Code Playgroud)

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

while(True):
    i += 1
    time.sleep(0)
Run Code Online (Sandbox Code Playgroud)

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

小智 24

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

import time

while True:
    print "loop"
    time.sleep(0.5)
Run Code Online (Sandbox Code Playgroud)

就像一个测试.运行此命令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)  
Run Code Online (Sandbox Code Playgroud)

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

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

编辑:所以我看看究竟是什么linux做的.do_select(fs\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);
Run Code Online (Sandbox Code Playgroud)

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

但是,我会重新尝试,对其他进程更好的方法通常涉及使用除自旋锁之外的其他机制.


mou*_*uad 12

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

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

现在floatsleep被设计为在不同的平台上工作,但是只要有可能,行为就被设计为类似的,但是因为我们只对类似unix的平台感兴趣,所以我们只检查那部分:

...
Py_BEGIN_ALLOW_THREADS
sleep((int)secs);
Py_END_ALLOW_THREADS
Run Code Online (Sandbox Code Playgroud)

正如你所看到的那样floatsleep是调用Csleep和sleep man页面:

sleep()函数将导致调用线程被暂停执行,直到参数seconds指定的实时秒数已经过去或...

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

那么这就是宏Py_BEGIN_ALLOW_THREADS和运行的地方Py_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.
Run Code Online (Sandbox Code Playgroud)

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

希望这有用.


Omn*_*ous 8

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