我们的代码是用C++ 11(VS2012/Win 7-64bit)编写的。 C++库提供了我们使用的sleep_for函数。我们观察到C++ sleep_for有时会显示较大的过冲。换句话说,我们要求 sleep 15毫秒,但事实证明该 sleep 是100毫秒当系统上的负载很高时,我们会看到这种情况。

我的第一个 react 是:“如果系统上有很多负载,而其他线程正在使用CPU,则 sleep 当然会“花费更长的时间”。
但是,“有趣”的事情是,如果我们用Windows API“Sleep”调用替换sleep_for,则看不到此行为。我还看到水下的sleep_for函数调用了Window API的Sleep方法。

sleep_for的文档指出:



因此,从技术上讲,该功能正在运行。但是,我们没想到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

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

此函数使线程放弃其剩余时间片,并在基于dwMilliseconds的值的间隔内变得无法运行。系统时钟以固定速率“滴答”。如果dwMilliseconds小于系统时钟的分辨率,则线程的 sleep 时间可能少于指定的时间长度。如果dwMilliseconds大于1个刻度但小于2个刻度,则等待时间可以介于1个和2个刻度之间,依此类推。为了提高 sleep 间隔的准确性,请调用timeGetDevCaps函数来确定支持的最小计时器分辨率,并调用timeBeginPeriod函数来将计时器分辨率设置为其最小值。调用timeBeginPeriod时请格外小心,因为频繁调用会严重影响系统时钟,系统电源使用情况和调度程序。如果您调用timeBeginPeriod,请在应用程序中提前将其调用一次,并确保在应用程序的末尾调用timeEndPeriod函数。

10-08 15:39