我的第一反应是:“当然,如果系统上有很多负载而其他线程正在使用cpu,那么睡眠”需要更长的时间“.
然而,“有趣”的是,如果我们用Windows API“Sleep”调用替换sleep_for,那么我们就不会看到这种行为.我还看到了水下的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)功能之间存在差异.
有人可以解释这种行为吗?
例如,调用SleepEx(15)在调试模式下生成以下程序集(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,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,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],DWORD PTR $T2[ebp+4]
mov DWORD PTR __Tgt$[ebp+4],DWORD PTR $T2[ebp+8]
mov DWORD PTR __Tgt$[ebp+8],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,COMDAT
; 758 : { // convert duration to xtime
push ebp
mov ebp,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,1000> >::zero
add esp,4
push eax
mov ecx,DWORD PTR __Rel_time$[ebp]
push ecx
call chronos_operator ; std::chrono::operator<=<__int64,1000>,__int64,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,10000000> > >::time_since_epoch
push eax
lea ecx,DWORD PTR __T0$8[ebp]
call duration ; std::chrono::duration<__int64,1000000000> >::duration<__int64,1000000000> ><__int64,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,void>
lea ecx,DWORD PTR $T4[ebp]
push ecx
lea ecx,DWORD PTR __T0$8[ebp]
call duration_ratio ; std::chrono::duration<__int64,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,1> >,1000000000> >
add esp,8
mov ecx,eax
call duration_count ; std::chrono::duration<__int64,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,1> >::duration<__int64,1> ><__int64,void>
push eax
lea ecx,DWORD PTR $T2[ebp]
call duration_ratio ; std::chrono::duration<__int64,1>,DWORD PTR $T2[ebp]
push ecx
lea ecx,1000000000> >::operator-=
; 772 : _Xt.nsec = (long)_T0.count();
lea ecx,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],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,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,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小于系统时钟的分辨率,则线程可能会睡眠时间少于指定的时间长度.如果dwMilliseconds大于一个tick但小于2,则等待可以是一到两个滴答之间的任何位置,依此类推.要提高休眠间隔的准确性,请调用timeGetDevCaps函数以确定支持的最小计时器分辨率,并调用timeBeginPeriod函数将计时器分辨率设置为最小值.调用timeBeginPeriod时要小心,因为频繁的调用会显着影响系统时钟,系统功耗和调度程序.如果你调用timeBeginPeriod,在应用程序的早期调用它一次,并确保在应用程序的最后调用timeEndPeriod函数.