Thread.Sleep(0)的妙用

2023-11-17

https://www.cnblogs.com/JianGuoWan/p/9139698.html

Thread.Sleep(0) 表示挂起0毫秒,你可能觉得没作用,你要写Thread.Sleep(1000) 就有感觉了。似乎毫无意义。

MSDN的说明:指定零 (0) 以指示应挂起此线程以使其他等待线程能够执行。

Thread.Sleep(0) 并非是真的要线程挂起0毫秒,意义在于这次调用Thread.Sleep(0)的当前线程确实的被冻结了一下,让其他线程有机会优先执行。Thread.Sleep(0) 是你的线程暂时放弃cpu,也就是释放一些未用的时间片给其他线程或进程使用,就相当于一个让位动作。
 

  1. Thread th = new Thread(new ThreadStart(MainForm.StartSplash));  
  2. th.Priority = ThreadPriority.AboveNormal;  
  3. th.Start();  
  4. Thread.Sleep(0);  
  5.   
  6.   
  7. base.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);  
  8. this.Initialize();  
Thread th = new Thread(new ThreadStart(MainForm.StartSplash));
th.Priority = ThreadPriority.AboveNormal;
th.Start();
Thread.Sleep(0);


base.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
this.Initialize();


在线程中,调用sleep(0)可以释放cpu时间,让线程马上重新回到就绪队列而非等待队列,sleep(0)释放当前线程所剩余的时间片(如果有剩余的话),这样可以让操作系统切换其他线程来执行,提升效率。

我们可能经常会用到 Thread.Sleep 函数来使线程挂起一段时间。那么你有没有正确的理解这个函数的用法呢?

思考下面这两个问题:

  1. 假设现在是 2017-4-7 12:00:00.000,如果我调用一下 Thread.Sleep(1000) ,在 2017-4-7 12:00:01.000 的时候,这个线程会 不会被唤醒?
  2. 某人的代码中用了一句看似莫明其妙的话:Thread.Sleep(0) 。既然是 Sleep 0 毫秒,那么他跟去掉这句代码相比,有啥区别么?

我们先回顾一下操作系统原理。

操作系统中,CPU竞争有很多种策略。Unix系统使用的是时间片算法,而Windows则属于抢占式的。
在时间片算法中,所有的进程排成一个队列。操作系统按照他们的顺序,给每个进程分配一段时间,即该进程允许运行的时间。如果在 时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。调度程 序所要做的就是维护一张就绪进程列表,,当进程用完它的时间片后,它被移到队列的末尾。
所谓抢占式操作系统,就是说如果一个进程得到了 CPU 时间,除非它自己放弃使用 CPU ,否则将完全霸占 CPU 。因此可以看出,在抢 占式操作系统中,操作系统假设所有的进程都是“人品很好”的,会主动退出 CPU 。在抢占式操作系统中,假设有若干进程,操作系统会根据他们的优先级、饥饿时间(已经多长时间没有使用过 CPU 了),给他们算出一 个总的优先级来。操作系统就会把 CPU 交给总优先级最高的这个进程。当进程执行完毕或者自己主动挂起后,操作系统就会重新计算一 次所有进程的总优先级,然后再挑一个优先级最高的把 CPU 控制权交给他。
我们用分蛋糕的场景来描述这两种算法。假设有源源不断的蛋糕(源源不断的时间),一副刀叉(一个CPU),10个等待吃蛋糕的人(10 个进程)。
如果是 Unix操作系统来负责分蛋糕,那么他会这样定规矩:每个人上来吃 1 分钟,时间到了换下一个。最后一个人吃完了就再从头开始。于是,不管这10个人是不是优先级不同、饥饿程度不同、饭量不同,每个人上来的时候都可以吃 1 分钟。当然,如果有人本来不太饿,或者饭量小,吃了30秒钟之后就吃饱了,那么他可以跟操作系统说:我已经吃饱了(挂起)。于是操作系统就会让下一个人接着来。
如果是 Windows 操作系统来负责分蛋糕的,那么场面就很有意思了。他会这样定规矩:我会根据你们的优先级、饥饿程度去给你们每个人计算一个优先级。优先级最高的那个人,可以上来吃蛋糕——吃到你不想吃为止。等这个人吃完了,我再重新根据优先级、饥饿程度来计算每个人的优先级,然后再分给优先级最高的那个人。

这样看来,这个场面就有意思了——可能有些人是PPMM,因此具有高优先级,于是她就可以经常来吃蛋糕。可能另外一个人是个丑男,而去很ws,所以优先级特别低,于是好半天了才轮到他一次(因为随着时间的推移,他会越来越饥饿,因此算出来的总优先级就会越来越高,因此总有一天会轮到他的)。而且,如果一不小心让一个大胖子得到了刀叉,因为他饭量大,可能他会霸占着蛋糕连续吃很久很久,导致旁边的人在那里咽口水。。。

而且,还可能会有这种情况出现:操作系统现在计算出来的结果,5号PPMM总优先级最高,而且高出别人一大截。因此就叫5号来吃蛋糕。5号吃了一小会儿,觉得没那么饿了,于是说“我不吃了”(挂起)。因此操作系统就会重新计算所有人的优先级。因为5号刚刚吃过,因此她的饥饿程度变小了,于是总优先级变小了;而其他人因为多等了一会儿,饥饿程度都变大了,所以总优先级也变大了。不过这时候仍然有可能5号的优先级比别的都高,只不过现在只比其他的高一点点——但她仍然是总优先级最高的啊。因此操作系统就会说:5号mm上来吃蛋糕……(5号mm心里郁闷,这不刚吃过嘛……人家要减肥……谁叫你长那么漂亮,获得了那么高的优先级)。

那么,Thread.Sleep 函数是干吗的呢?还用刚才的分蛋糕的场景来描述。上面的场景里面,5号MM在吃了一次蛋糕之后,觉得已经有8分饱了,她觉得在未来的半个小时之内都不想再来吃蛋糕了,那么她就会跟操作系统说:在未来的半个小时之内不要再叫我上来吃蛋糕了。这样,操作系统在随后的半个小时里面重新计算所有人总优先级的时候,就会忽略5号mm。Sleep函数就是干这事的,他告诉操作系统“在未来的多少毫秒内我不参与CPU竞争”。
看完了 Thread.Sleep 的作用,我们再来想想文章开头的两个问题。
对于第一个问题,答案是:不一定。因为你只是告诉操作系统:在未来的1000毫秒内我不想再参与到CPU竞争。那么1000毫秒过去之后,这时候也许另外一个线程正在使用CPU,那么这时候操作系统是不会重新分配CPU的,直到那个线程挂起或结束;况且,即使这个时候恰巧轮到操作系统进行CPU 分配,那么当前线程也不一定就是总优先级最高的那个,CPU还是可能被其他线程抢占去。与此相似的,Thread有个Resume函数,是用来唤醒挂起的线程的。好像上面所说的一样,这个函数只是“告诉操作系统我从现在起开始参与CPU竞争了”,这个函数的调用并不能马上使得这个线程获得CPU控制权。
对于第二个问题,答案是:有,而且区别很明显。假设我们刚才的分蛋糕场景里面,有另外一个PPMM 7号,她的优先级也非常非常高(因为非常非常漂亮),所以操作系统总是会叫道她来吃蛋糕。而且,7号也非常喜欢吃蛋糕,而且饭量也很大。不过,7号人品很好,她很善良,她没吃几口就会想:如果现在有别人比我更需要吃蛋糕,那么我就让给他。因此,她可以每吃几口就跟操作系统说:我们来重新计算一下所有人的总优先级吧。不过,操作系统不接受这个建议——因为操作系统不提供这个接口。于是7号mm就换了个说法:“在未来的0毫秒之内不要再叫我上来吃蛋糕了”。这个指令操作系统是接受的,于是此时操作系统就会重新计算大家的总优先级——注意这个时候是连7号一起计算的,因为“0毫秒已经过去了”嘛。因此如果没有比7号更需要吃蛋糕的人出现,那么下一次7号还是会被叫上来吃蛋糕。
因此,Thread.Sleep(0)的作用,就是“触发操作系统立刻重新进行一次CPU竞争”。竞争的结果也许是当前线程仍然获得CPU控制权,也许会换成别的线程获得CPU控制权。这也是我们在大循环里面经常会写一句Thread.Sleep(0) ,因为这样就给了其他线程比如Paint线程获得CPU控制权的权力,这样界面就不会假死在那里。
末了说明一下,虽然上面提到说“除非它自己放弃使用 CPU ,否则将完全霸占 CPU”,但这个行为仍然是受到制约的——操作系统会监控你霸占CPU的情况,如果发现某个线程长时间霸占CPU,会强制使这个线程挂起,因此在实际上不会出现“一个线程一直霸占着 CPU 不放”的情况。至于我们的大循环造成程序假死,并不是因为这个线程一直在霸占着CPU。实际上在这段时间操作系统已经进行过多次CPU竞争了,只不过其他线程在获得CPU控制权之后很短时间内马上就退出了,于是就又轮到了这个线程继续执行循环,于是就又用了很久才被操作系统强制挂起。。。因此反应到界面上,看起来就好像这个线程一直在霸占着CPU一样。
末了再说明一下,文中线程、进程有点混乱,其实在Windows原理层面,CPU竞争都是线程级的,本文中把这里的进程、线程看成同一个东西就好了。


问题:主动的放弃运行让系统调度的意义是什么呢?

为了等待资源、事件,那么你需要进入等待队列。如果你已经拥有运行所需资源,却让系统调度,这是资源的浪费,并且调度也是要浪费资源的

解释:对的,你要等待资源,你确实需要排队,假如AB两个线程为合作关系,A线程处理一些原始数据,数据处理到一定程度,交给B线程处理,在A处理原始数据的时候,B也要做一些准备工作,所以,AB是并发的,但是B做好准备之后,需要等待A处理好那些数据,接过A的数据,继续处理,因此,这个等待,如果A不使用信号或者等待条件来通知B的话,那么B必须一直轮询,查看A是否已完成,B线程所做的这个轮询是否会一直占用CPU来做无用的循环查看呢?因此B这个时候占用的cpu时间片做的是无用功,因此,这里sleep(0)就有作用,当B查看A没处理完数据的时候,B马上sleep(0)交出B的时间片,让操作系统调度A来运行(假设只有AB两个线程),那么这个时候,A就会得到充分的时间来处理它的数据,这个不是一个应用了吗?我猜测pthread_conn_wait()内部阻塞就是使用这个机制
 

  1. thread_fun()  
  2. {  
  3.     prepare_word.....  
  4.   
  5.   
  6.     while (1)  
  7.     {  
  8.         if (A is finish)  
  9.             break;  
  10.         else  
  11.             sleep(0); //这里会交出B的时间片,下一次调度B的时候,接着执行这个循环  
  12.     }  
  13.   
  14.   
  15.     process A's data  
  16. }  
thread_fun()
{
    prepare_word.....


    while (1)
    {
        if (A is finish)
            break;
        else
            sleep(0); //这里会交出B的时间片,下一次调度B的时候,接着执行这个循环
    }


    process A's data
}



没有sleep(0)版:

  1. thread_fun()  
  2. {  
  3.     prepare_word.....  
  4.   
  5.   
  6.     while (1)  //这里会一直浪费CPU时间做死循环的轮询,无用功  
  7.     {  
  8.         if (A is finish)  
  9.             break;  
  10.     }  
  11.   
  12.   
  13.     process A's data  
  14. }  
thread_fun()
{
    prepare_word.....


    while (1)  //这里会一直浪费CPU时间做死循环的轮询,无用功
    {
        if (A is finish)
            break;
    }


    process A's data
}


如果说是轮询,那它就是一种高效、节约、谦虚的轮询,如果没有sleep(0),那么B线程可能会执行上万次的while循环,直至它的时间片消耗完,做这些都是无用功,而是用了sleep(0)后,B线程每一次执行就只做一次while循环就把剩余的时间片让出给A,能让A得到更多的执行次数,利用率更高

总结:

在线程没退出之前,线程有三个状态,就绪态,运行态,等待态。sleep(n)之所以在n秒内不会参与CPU竞争,是因为,当线程调用sleep(n)的时候,线程是由运行态转入等待态,线程被放入等待队列中,等待定时器n秒后的中断事件,当到达n秒计时后,线程才重新由等待态转入就绪态,被放入就绪队列中,等待队列中的线程是不参与cpu竞争的,只有就绪队列中的线程才会参与cpu竞争,所谓的cpu调度,就是根据一定的算法(优先级,FIFO等。。。),从就绪队列中选择一个线程来分配cpu时间。

而sleep(0)之所以马上回去参与cpu竞争,是因为调用sleep(0)后,因为0的原因,线程直接回到就绪队列,而非进入等待队列,只要进入就绪队列,那么它就参与cpu竞争。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Thread.Sleep(0)的妙用 的相关文章

随机推荐

  • android接口加密实现方案

    Fiddler抓包工具配置 android接口如何防抓取 Windows下使用OpenSSL生成RSA证书 文中使用的加密库 1 程序主类 import android os Bundle import android support v7
  • QTreeWidget表头拉伸宽度自适应导致长度显示有遮挡

    对QTreeWidget 设置 header gt setSectionResizeMode 0 QHeaderView ResizeToContents 后 由于控件的拉伸属性再为其设置setColumnWidth 0 200 将不再生效
  • CE+OD外挂制作实战 [提高篇]

    人造指针 基址 实验目标 通过向游戏注入一段特殊汇编代码 实现自动获取动态地址 省略找基址的麻烦 为什么会出现人造指针 1 基址偏移层数太多 很难找 2 有些游戏根本找不到基址 人造指针有什么优缺点 1 人造指针就算游戏更新也无需去重复找基
  • Numpy 通过矩阵操作避免for循环 之 [None, :, :]运用

    我们知道python中 for 循环的效率是不高的 相比之下 直接运用矩阵向量操作 如点乘 可以让代码运行更快 因为在复现paper时候遇到了这样的问题 在这里总结一下我的解决方法 None 介绍 通过None indexing 可以将一个
  • citespace数据处理:用python对Ref文档进行去重

    首先将txt文档提取到Excel表格中筛选出重复项 并且整理到txt中 需要去重的目标txt也准备好 接下来运行代码 注意代码中的路径需要自己按实际情况修改 coding utf 8 author pig 实现去重功能 import re
  • xampp安装

    常见报错 1 apache启动出错 ServerRoot must be a valid directory 解决方法 安装时 直接安装在D盘下 而不是子文件夹下 2 XAMPP与VMware端口443占用问题 Port 443 in us
  • LaTeX新人教程,30分钟从完全陌生到基本入门

    1 LaTeX软件的安装和使用 方法A 自助 在MikTeX的官网下载免费的MikTeX编译包并安装 下载WinEdt 收费 或TexMaker 免费 等编辑界面软件并安装 方法B 打包 在ctex org下载ctex套装 含MikTeX及
  • C++,函数的分文件编写,进行封装。

    1 创建后缀名为 h的头文件 例如test h 右键头文件 新建项 下方文件命名为为 h的头文件 2 然后在头文件里面 对函数声明 其中 include
  • 160道Shell企业面试题整理汇总

    1 Shell脚本是什么 它是必需的吗 答 一个Shell脚本是一个文本文件 包含一个或多个命令 作为系统管理员 我们经常需要使用多个命令来完成一项任务 我们可以添加这些所有命令在一个文本文件 Shell脚本 来完成这些日常工作任务 2 什
  • 领扣算法:234 回文链表

    请判断一个链表是否为回文链表 示例 1 输入 1 gt 2 输出 false 示例 2 输入 1 gt 2 gt 2 gt 1 输出 true 输入参数 public boolean isPalindrome ListNode head 解
  • 软件设计七大原则

    文章目录 一 开闭原则 定义 实现方法 二 里氏替换原则 定义 实现方法 三 依赖倒置原则 定义 实现方法 四 单一职责原则 定义 实现方法 五 接口隔离原则 定义 实现方法 六 迪米特法则 定义 实现方法 七 合成复用原则 定义 实现方法
  • 图像处理——基本知识

    数字图像处理的一些基本知识 一 什么是灰度直方图 二 什么是局部处理 哪些算法属于局部处理 三 什么是点处理 哪些算法属于点处理 四 图像增强常用的方法 五 锐化和平滑 六 膨胀和腐蚀 七 无监督算法 一 什么是灰度直方图 灰度直方图反映的
  • liunx卸载磁盘后重新挂载

    umount dev sdb1 root localhost mkdir data1 在根目录下新建挂载硬盘所需文件夹 root localhost parted dev sdb 使用parted来对GPT磁盘操作 进入交互式模式 part
  • ReactNative系列之十二图标组件react-native-vector-icons的使用

    1 前言 第三方网站提供了丰富的矢量图icon 这些图可以在我们的应用中被引入使用 这时介绍react native vector icons控件的使用 2 安装 请移步至https github com oblador react nat
  • Jenkins构建Maven多模块项目时,单独编译子模块,并且不触发构建其它模块

    通过下面两个步骤 可以实现对个别模块的编译 一 Jenkins构建Maven多模块项目时 单独编译子模块 配置 1 Root POM指向父pom xml 2 Goals and options指定构建模块的参数 mvn pl jsoft w
  • 一条自由游动的鲸鱼

    先看效果 再看代码 div div
  • R语言画图的实例分析,坐标轴,图例等设置

    通过一个具体的案例对R语言的画图进行分析 主要包括辅助线的添加 plot 函数的基本参数设置 以及图例坐标轴的设置 案例分析 数据的输入 dose lt c 20 30 40 45 60 drugA lt c 16 20 27 40 60
  • 关于docker和podman

    docker daemon 存在问题 单点故障问题 docker daemon一旦死亡 所有容器都将死亡 docker daemon 拥有运行中的容器的所有子进程 所有docker操作都需由具有跟root相同权限的用户执行 构建容器时 可能
  • 【华为OD机试】按身高和体重排队(C++ Python Java)2023 B卷

    时间限制 C C 1秒 其他语言 2秒 空间限制 C C 262144K 其他语言524288K 64bit IO Format lld 语言限定 C clang11 C clang 11 Pascal fpc 3 0 2 Java jav
  • Thread.Sleep(0)的妙用

    https www cnblogs com JianGuoWan p 9139698 html Thread Sleep 0 表示挂起0毫秒 你可能觉得没作用 你要写Thread Sleep 1000 就有感觉了 似乎毫无意义 MSDN的说