读 dotnet 源代码 为何 Thread.Sleep 半毫秒和一毫秒等待时间差距如此之大

news/2024/10/12 16:29:51

本文记录我读 dotnet 的源代码了解到为什么调用 Thread.Sleep 的时候,传入的是不足一毫秒,如半毫秒时或 0.99 毫秒,与传入是一毫秒时,两者的等待时间差距非常大

大概如下的代码,分别进行两次传入给 Thread.Sleep 不同等待时间的循环测试。其中一次传入的是 0.99 毫秒,一次传入的是 1 毫秒

using System.Diagnostics;var stopwatch = Stopwatch.StartNew();for (int i = 0; i < 1000; i++)
{Thread.Sleep(TimeSpan.FromMilliseconds(0.99));
}stopwatch.Stop();Console.WriteLine($"耗时:{stopwatch.ElapsedMilliseconds}ms");stopwatch.Restart();
for (int i = 0; i < 1000; i++)
{Thread.Sleep(TimeSpan.FromMilliseconds(1));
}
Console.WriteLine($"耗时:{stopwatch.ElapsedMilliseconds}ms");

在我的设备上运行的输出内容如下

耗时:0ms
耗时:15665ms

通过如上代码可以看到传入 0.99 毫秒时,居然接近统计不出来其耗时

而传入 1 毫秒时,由于在 Windows 下的最低 Thread.Sleep 时间大概在 15-16毫秒 左右,于是差不多是 15 秒左右的时间,这是符合预期的。即写入 Thread.Sleep(TimeSpan.FromMilliseconds(1)); 也可能差不多等待 15 毫秒的量程时间

那为什么 0.99 毫秒和 1 毫秒只差大概 0.1 毫秒的时间,却在等待过程中有如此长的时间差距

通过阅读 dotnet 的源代码,可以看到 Thread.Sleep 的实现代码大概如下

namespace System.Threading
{public sealed partial class Thread{... // 忽略其他代码public static void Sleep(TimeSpan timeout) => Sleep(WaitHandle.ToTimeoutMilliseconds(timeout));}
}namespace System.Threading
{public abstract partial class WaitHandle : MarshalByRefObject, IDisposable{internal static int ToTimeoutMilliseconds(TimeSpan timeout){long timeoutMilliseconds = (long)timeout.TotalMilliseconds;... // 忽略其他代码return (int)timeoutMilliseconds;}... // 忽略其他代码}
}

通过以上可以可见,这是直接将 TotalMilliseconds 强行转换为 int 类型,换句话说就是不到 1 毫秒的,都会被转换为 0 毫秒的值

于是即使是 0.99 毫秒,在这里的转换之下,依然会返回 0 毫秒回去

而 Thread.Sleep 底层里面专门为传入 0 毫秒做了特殊处理,将会进入自旋逻辑。大家都知道,进入自旋时,自旋的速度是非常快的

以上的 Thread.Sleep(TimeSpan.FromMilliseconds(0.99)); 代码和 Thread.Sleep(0) 在执行上等价的,意味着第一次只执行了一千次自旋,自然就几乎测试不出来耗时了

在 Windows 下的 Thread.Sleep 底层代码是写在 Thread.Windows.cs 代码里的,实现如下

namespace System.Threading
{public sealed partial class Thread{internal static void UninterruptibleSleep0() => Interop.Kernel32.Sleep(0);#if !CORECLRprivate static void SleepInternal(int millisecondsTimeout){Debug.Assert(millisecondsTimeout >= -1);Interop.Kernel32.Sleep((uint)millisecondsTimeout);}
#endif... // 忽略其他代码}
}

如上面代码,底层为 Kernel32 的 Sleep 函数,如官方文档所述,传入 0 是特殊的实现逻辑

If you specify 0 milliseconds, the thread will relinquish the remainder of its time slice but remain ready.

因此如果在 Thread.Sleep 方法里面传入的 TimeSpan 不足一毫秒,那就和传入 0 毫秒是相同的执行逻辑

更多基础技术博客,请参阅 博客导航

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/53311.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

从零开始学会建网站,个人博客建设!一步步全程图文教程。

第一步,需要购买一个域名,当然,若是测试只用也可用ip暂时替代。 比如 .com .cn .net 等域名,比如以本站:70zhan.com 为例,70zhan是我选择的域名,而.com后缀是国际域名,目前推荐的国际域名后缀包括:.com .net .org,如果是国内则可以选择.cn! 目前国际域名都比较贵,…

如何用英语读出所有数字 All In One

如何用英语读出所有数字 All In One 大数/小数/序数/分数/日期/地址/电话如何用英语读出所有数字 All In One大数/小数/序数/分数/日期/地址/电话号码图解大数基数词序数词小数⚠️ 根据语境,区分 $4.99 与 $499分数日期地址电话号码demosHow to read ALL NUMBERS in English…

PBXGroup attempted to initialize an object with unknown ISA PBXFileSystemSynchronizedRootGroup

热烈欢迎,请直接点击!!! 进入博主App Store主页,下载使用各个作品!!! 注:博主将坚持每月上线一个新app!! 在Xcode中,Widget文件夹右键点击,弹出菜单,在菜单中选择【Convert to Group】,即可正常使用Cocoapods。

全网最适合入门的面向对象编程教程:39 Python常用复合数据类型-集合

在Python中,集合(set)是一种常用的复合数据类型。集合是一组无序且不重复的元素。与列表和元组不同,集合中的元素是无序的,并且每个元素只能出现一次。全网最适合入门的面向对象编程教程:39 Python 常用复合数据类型-集合摘要: 在 Python 中,集合(set)是一种常用的复…

记录一道 sql 注入流量分析题

学习一下下!题目:一个流量包 ddos.pcapng在ddos中寻找黑客的真实意图。提交flag格式:flag{xxxx)。思路: 分析一下流量包,过了 http 请求,不难发现,黑客在尝试登入,那肯定就是在破解密码了,那我们可以看看登入成功的包​ 然后,继续往下看流量包,可以发现登入成功的数…

线性规划标准型知识精解

线性规划的标准型及其转化过程是理解和求解线性规划问题的基础。通过引入松弛变量、剩余变量和将自由变量转化为两个非负变量,可以将任意形式的线性规划问题转化为标准型。标准型的线性规划问题便于使用单纯形法等算法进行求解,从而找到最优解。了解这些概念和技巧,对于深入…

高并发业务下的库存扣减技术方案设计

扣减库存需要查询库存是否足够:足够就占用库存 不够则返回库存不足(这里不区分库存可用、占用、已消耗等状态,统一成扣减库存数量,简化场景)并发场景,若 查询库存和扣减库存不具备原子性,就可能超卖,而高并发场景超卖概率会增高,超卖数额也会增高。处理超卖的确麻烦:系…

Echarts实现双x轴,支持均分和非均分的情况

效果图代码 <template><div class="app"><div class="demo" ref="demoRef"></div></div> </template><script> import * as echarts from echarts export default {data() {return {}},mounted() {th…