c++ - Windows 7:超越 C++ std::this_thread::sleep_for

标签 c++ windows winapi sleep

我们的代码是用 C++ 11(VS2012/Win 7-64 位)编写的。 C++ 库提供了我们使用的 sleep_for 函数。我们观察到 C++ sleep_for 有时会出现较大的超调。换句话说,我们要求 sleep 15 毫秒,但 sleep 结果是例如100 毫秒。当系统负载很高时,我们会看到这一点。

我的第一 react 是:“当然,如果系统负载很大并且其他线程正在使用 CPU,那么 sleep 当然会“花更长的时间””。 然而,“有趣”的是,如果我们将 sleep_for 替换为 Windows API“Sleep”调用,那么我们将看不到这种行为。我还看到水下的 sleep_for 函数调用了 Window API Sleep 方法。

sleep_for 的文档状态:

The function blocks the calling thread for at least the time that's specified by Rel_time. This function does not throw any exceptions.

所以从技术上讲,该功能正在运行。然而,我们没想到会看到 C++ sleep_for 和常规 Sleep(Ex) 函数之间的区别。

有人可以解释这种行为吗?

最佳答案

如果使用 sleep_for 与 SleepEx,则会执行相当多的额外代码。

例如,调用 SleepEx(15) 在 Debug模式下生成以下程序集 (Visual Studio 2015):

; 9    :    SleepEx(15, false);

    mov esi, esp
    push    0
    push    15                  ; 0000000fH
    call    DWORD PTR __imp__SleepEx@8
    cmp esi, esp
    call    __RTC_CheckEsp

对比这段代码

const std::chrono::milliseconds duration(15);
std::this_thread::sleep_for(duration);

生成以下内容:

; 9    :    std::this_thread::sleep_for(std::chrono::milliseconds(15));

    mov DWORD PTR $T1[ebp], 15          ; 0000000fH
    lea eax, DWORD PTR $T1[ebp]
    push    eax
    lea ecx, DWORD PTR $T2[ebp]
    call    duration
    push    eax
    call    sleep_for
    add esp, 4

这调用到:

duration PROC ; std::chrono::duration<__int64,std::ratio<1,1000> >::duration<__int64,std::ratio<1,1000> ><int,void>, COMDAT
; _this$ = ecx

; 113  :        {   // construct from representation

    push    ebp
    mov ebp, esp
    sub esp, 204                ; 000000ccH
    push    ebx
    push    esi
    push    edi
    push    ecx
    lea edi, DWORD PTR [ebp-204]
    mov ecx, 51                 ; 00000033H
    mov eax, -858993460             ; ccccccccH
    rep stosd
    pop ecx
    mov DWORD PTR _this$[ebp], ecx

; 112  :            : _MyRep(static_cast<_Rep>(_Val))

    mov eax, DWORD PTR __Val$[ebp]
    mov eax, DWORD PTR [eax]
    cdq
    mov ecx, DWORD PTR _this$[ebp]
    mov DWORD PTR [ecx], eax
    mov DWORD PTR [ecx+4], edx

; 114  :        }

    mov eax, DWORD PTR _this$[ebp]
    pop edi
    pop esi
    pop ebx
    mov esp, ebp
    pop ebp
    ret 4
duration ENDP

并呼入

    sleep_for PROC ; std::this_thread::sleep_for<__int64,std::ratio<1,1000> >, COMDAT

    ; 151  :    {   // sleep for duration

        push    ebp
        mov ebp, esp
        sub esp, 268                ; 0000010cH
        push    ebx
        push    esi
        push    edi
        lea edi, DWORD PTR [ebp-268]
        mov ecx, 67                 ; 00000043H
        mov eax, -858993460             ; ccccccccH
        rep stosd
        mov eax, DWORD PTR ___security_cookie
        xor eax, ebp
        mov DWORD PTR __$ArrayPad$[ebp], eax

    ; 152  :    stdext::threads::xtime _Tgt = _To_xtime(_Rel_time);

        mov eax, DWORD PTR __Rel_time$[ebp]
        push    eax
        lea ecx, DWORD PTR $T1[ebp]
        push    ecx
        call    to_xtime
        add esp, 8
        mov edx, DWORD PTR [eax]
        mov DWORD PTR $T2[ebp], edx
        mov ecx, DWORD PTR [eax+4]
        mov DWORD PTR $T2[ebp+4], ecx
        mov edx, DWORD PTR [eax+8]
        mov DWORD PTR $T2[ebp+8], edx
        mov eax, DWORD PTR [eax+12]
        mov DWORD PTR $T2[ebp+12], eax
        mov ecx, DWORD PTR $T2[ebp]
        mov DWORD PTR __Tgt$[ebp], ecx
        mov edx, DWORD PTR $T2[ebp+4]
        mov DWORD PTR __Tgt$[ebp+4], edx
        mov eax, DWORD PTR $T2[ebp+8]
        mov DWORD PTR __Tgt$[ebp+8], eax
        mov ecx, DWORD PTR $T2[ebp+12]
        mov DWORD PTR __Tgt$[ebp+12], ecx

    ; 153  :    sleep_until(&_Tgt);

        lea eax, DWORD PTR __Tgt$[ebp]
        push    eax
        call    sleep_until
        add esp, 4

    ; 154  :    }

        push    edx
        mov ecx, ebp
        push    eax
        lea edx, DWORD PTR $LN5@sleep_for
        call    @_RTC_CheckStackVars@8
        pop eax
        pop edx
        pop edi
        pop esi
        pop ebx
        mov ecx, DWORD PTR __$ArrayPad$[ebp]
        xor ecx, ebp
        call    @__security_check_cookie@4
        add esp, 268                ; 0000010cH
        cmp ebp, esp
        call    __RTC_CheckEsp
        mov esp, ebp
        pop ebp
        ret 0
        npad    3
    $LN5@sleep_for:
        DD  1
        DD  $LN4@sleep_for
    $LN4@sleep_for:
        DD  -24                 ; ffffffe8H
        DD  16                  ; 00000010H
        DD  $LN3@sleep_for
    $LN3@sleep_for:
        DB  95                  ; 0000005fH
        DB  84                  ; 00000054H
        DB  103                 ; 00000067H
        DB  116                 ; 00000074H
        DB  0
    sleep_for ENDP

一些转换发生了:

to_xtime PROC ; std::_To_xtime<__int64,std::ratio<1,1000> >, COMDAT

; 758  :    {   // convert duration to xtime

    push    ebp
    mov ebp, esp
    sub esp, 348                ; 0000015cH
    push    ebx
    push    esi
    push    edi
    lea edi, DWORD PTR [ebp-348]
    mov ecx, 87                 ; 00000057H
    mov eax, -858993460             ; ccccccccH
    rep stosd
    mov eax, DWORD PTR ___security_cookie
    xor eax, ebp
    mov DWORD PTR __$ArrayPad$[ebp], eax

; 759  :    xtime _Xt;
; 760  :    if (_Rel_time <= chrono::duration<_Rep, _Period>::zero())

    lea eax, DWORD PTR $T7[ebp]
    push    eax
    call    duration_zero ; std::chrono::duration<__int64,std::ratio<1,1000> >::zero
    add esp, 4
    push    eax
    mov ecx, DWORD PTR __Rel_time$[ebp]
    push    ecx
    call    chronos_operator ; std::chrono::operator<=<__int64,std::ratio<1,1000>,__int64,std::ratio<1,1000> >
    add esp, 8
    movzx   edx, al
    test    edx, edx
    je  SHORT $LN2@To_xtime

; 761  :        {   // negative or zero relative time, return zero
; 762  :        _Xt.sec = 0;

    xorps   xmm0, xmm0
    movlpd  QWORD PTR __Xt$[ebp], xmm0

; 763  :        _Xt.nsec = 0;

    mov DWORD PTR __Xt$[ebp+8], 0

; 764  :        }
; 765  :    else

    jmp $LN3@To_xtime
$LN2@To_xtime:

; 766  :        {   // positive relative time, convert
; 767  :        chrono::nanoseconds _T0 =
; 768  :            chrono::system_clock::now().time_since_epoch();

    lea eax, DWORD PTR $T5[ebp]
    push    eax
    lea ecx, DWORD PTR $T6[ebp]
    push    ecx
    call    system_clock_now ; std::chrono::system_clock::now
    add esp, 4
    mov ecx, eax
    call    time_since_ephoch ; std::chrono::time_point<std::chrono::system_clock,std::chrono::duration<__int64,std::ratio<1,10000000> > >::time_since_epoch
    push    eax
    lea ecx, DWORD PTR __T0$8[ebp]
    call    duration ; std::chrono::duration<__int64,std::ratio<1,1000000000> >::duration<__int64,std::ratio<1,1000000000> ><__int64,std::ratio<1,10000000>,void>

; 769  :        _T0 += _Rel_time;

    mov eax, DWORD PTR __Rel_time$[ebp]
    push    eax
    lea ecx, DWORD PTR $T4[ebp]
    call    duration_ratio ; std::chrono::duration<__int64,std::ratio<1,1000000000> >::duration<__int64,std::ratio<1,1000000000> ><__int64,std::ratio<1,1000>,void>
    lea ecx, DWORD PTR $T4[ebp]
    push    ecx
    lea ecx, DWORD PTR __T0$8[ebp]
    call    duration_ratio ; std::chrono::duration<__int64,std::ratio<1,1000000000> >::operator+=

; 770  :        _Xt.sec = chrono::duration_cast<chrono::seconds>(_T0).count();

    lea eax, DWORD PTR __T0$8[ebp]
    push    eax
    lea ecx, DWORD PTR $T3[ebp]
    push    ecx
    call    duration_cast ; std::chrono::duration_cast<std::chrono::duration<__int64,std::ratio<1,1> >,__int64,std::ratio<1,1000000000> >
    add esp, 8
    mov ecx, eax
    call    duration_count ; std::chrono::duration<__int64,std::ratio<1,1> >::count
    mov DWORD PTR __Xt$[ebp], eax
    mov DWORD PTR __Xt$[ebp+4], edx

; 771  :        _T0 -= chrono::seconds(_Xt.sec);

    lea eax, DWORD PTR __Xt$[ebp]
    push    eax
    lea ecx, DWORD PTR $T1[ebp]
    call    duration_ratio ; std::chrono::duration<__int64,std::ratio<1,1> >::duration<__int64,std::ratio<1,1> ><__int64,void>
    push    eax
    lea ecx, DWORD PTR $T2[ebp]
    call    duration_ratio ; std::chrono::duration<__int64,std::ratio<1,1000000000> >::duration<__int64,std::ratio<1,1000000000> ><__int64,std::ratio<1,1>,void>
    lea ecx, DWORD PTR $T2[ebp]
    push    ecx
    lea ecx, DWORD PTR __T0$8[ebp]
    call    duration_ratio ; std::chrono::duration<__int64,std::ratio<1,1000000000> >::operator-=

; 772  :        _Xt.nsec = (long)_T0.count();

    lea ecx, DWORD PTR __T0$8[ebp]
    call    duration_ratio ; std::chrono::duration<__int64,std::ratio<1,1000000000> >::count
    mov DWORD PTR __Xt$[ebp+8], eax
$LN3@To_xtime:

; 773  :        }
; 774  :    return (_Xt);

    mov eax, DWORD PTR $T9[ebp]
    mov ecx, DWORD PTR __Xt$[ebp]
    mov DWORD PTR [eax], ecx
    mov edx, DWORD PTR __Xt$[ebp+4]
    mov DWORD PTR [eax+4], edx
    mov ecx, DWORD PTR __Xt$[ebp+8]
    mov DWORD PTR [eax+8], ecx
    mov edx, DWORD PTR __Xt$[ebp+12]
    mov DWORD PTR [eax+12], edx
    mov eax, DWORD PTR $T9[ebp]

; 775  :    }

    push    edx
    mov ecx, ebp
    push    eax
    lea edx, DWORD PTR $LN8@To_xtime
    call    @_RTC_CheckStackVars@8
    pop eax
    pop edx
    pop edi
    pop esi
    pop ebx
    mov ecx, DWORD PTR __$ArrayPad$[ebp]
    xor ecx, ebp
    call    @__security_check_cookie@4
    add esp, 348                ; 0000015cH
    cmp ebp, esp
    call    __RTC_CheckEsp
    mov esp, ebp
    pop ebp
    ret 0
$LN8@To_xtime:
    DD  2
    DD  $LN7@To_xtime
$LN7@To_xtime:
    DD  -24                 ; ffffffe8H
    DD  16                  ; 00000010H
    DD  $LN5@To_xtime
    DD  -40                 ; ffffffd8H
    DD  8
    DD  $LN6@To_xtime
$LN6@To_xtime:
    DB  95                  ; 0000005fH
    DB  84                  ; 00000054H
    DB  48                  ; 00000030H
    DB  0
$LN5@To_xtime:
    DB  95                  ; 0000005fH
    DB  88                  ; 00000058H
    DB  116                 ; 00000074H
    DB  0
to_xtime ENDP 

最终会调用导入的函数,与 SleepEx 使用的函数相同。

sleep_until PROC    ; std::this_thread::sleep_until, COMDAT

; 131  :    {   // sleep until _Abs_time

    push    ebp
    mov ebp, esp
    sub esp, 192                ; 000000c0H
    push    ebx
    push    esi
    push    edi
    lea edi, DWORD PTR [ebp-192]
    mov ecx, 48                 ; 00000030H
    mov eax, -858993460             ; ccccccccH
    rep stosd

; 132  :    _Thrd_sleep(_Abs_time);

    mov esi, esp
    mov eax, DWORD PTR __Abs_time$[ebp]
    push    eax
    call    DWORD PTR __imp___Thrd_sleep
    add esp, 4
    cmp esi, esp
    call    __RTC_CheckEsp

; 133  :    }

    pop edi
    pop esi
    pop ebx
    add esp, 192                ; 000000c0H
    cmp ebp, esp
    call    __RTC_CheckEsp
    mov esp, ebp
    pop ebp
    ret 0
sleep_until ENDP

您还应该知道,根据 MSDN 文档 https://msdn.microsoft.com/en-us/library/windows/desktop/ms686307(v=vs.85).aspx,即使 SleepEx 也可能无法给出 100% 准确的结果。

此函数会导致线程放弃其时间片的剩余部分,并在基于 dwMilliseconds 值的时间间隔内变得不可运行。系统时钟以恒定速率“滴答”。如果 dwMilliseconds 小于系统时钟的分辨率,线程可能会休眠少于指定的时间长度。如果 dwMilliseconds 大于 1 个滴答声但小于 2 个滴答声,则等待时间可以在 1 到 2 个滴答声之间的任何时间,依此类推。要提高 sleep 间隔的准确性,请调用 timeGetDevCaps 函数来确定支持的最小计时器分辨率,并调用 timeBeginPeriod 函数将计时器分辨率设置为其最小值。调用 timeBeginPeriod 时要小心,因为频繁调用会显着影响系统时钟、系统电源使用和调度程序。如果您调用 timeBeginPeriod,请在应用程序的早期调用一次,并确保在应用程序的最后调用 timeEndPeriod 函数。

关于c++ - Windows 7:超越 C++ std::this_thread::sleep_for,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32904371/

相关文章:

在 Linux 主机上运行 XP 客户机的 Oracle Virtual Box 上的 C++ 编译器

c++ - 在 vector 上使用基于范围的 for 循环时,CppCoreChecker C 样式转换警告

c# - 添加应用程序 list 以确保跨 Windows XP、Vista 和 7 提升权限的可靠性如何?

PHP,杀死Windows进程?

c# - Windows 10 支持的操作系统 GUID 是什么?

c# - Keyboard.GetKeyStates 误报,我如何真正知道是否按下了某个键?

c++ - C++ int数组中的foreach

c++ - 从共享指针的取消引用值中获取共享指针

python - 如何在 Python 中获取主窗口的句柄?

python - 使用python通过windows服务打开另一个程序