关于线程调度和线程切换
背景知识:
- 调度:就是选出下一个要在cpu上执行的线程。
- 优先级:“剥夺”式调度的依据,windows将线程的优先级分为32个级别,0最低,31最高
- 就绪队列:windows系统中,每一个cpu有32个队列,每一个队列对应一个优先级,一个线程变成可调度,要么成为standingby线程,要么会按照他的优先级挂入相应队列的尾部。
- Standingby线程:当前线程的“剥夺”者,马上会得到cpu的执行权。
- 高优先级的线程将立刻抢得cpu
- 较高优先级的程序不退出调度,较低优先级的程序将不会得到cpu
- 在cpu上运行的线程被赋予一个时间片,除非被高优先级的线程“剥夺”,将一直运行下去,直到时间片用完
- 如果有相同优先级可调度线程存在,用完时间片的线程将被时钟剥夺cpu。
一、线程的调度:
- 当前线程主动让出剩余的运行时间
该调度是当前线程主动让出剩余运行时间,如执行了Sleep,Wait一个没有授信的对象等等 - 有新的线程变得可调度
比如一个被挂起的线程被中断处理程序激活,变为可调度,要进行一次调度,中断处理程序一般会将该线程的优先级和当前线程比较,如果优先级较高,该线程将成为抢夺者,否则该线程按照其优先级挂入相应的就绪队列。 - 就绪线程的优先级提高
如当前线程将一个就绪线程的级别提高,将引发一次调度,如果该线程的优先级比当前cpu上的线程的优先级高,该线程就成为剥夺者。 - 调度时钟中断
每一次时钟中断,都会将当前线程的定量减少一定值,如果发现当前线程的时间片已经用完,就会进行一次调度,如果和当前线程优先级对应的就绪队列非空,该排在该就绪队列最面的线程将会得到调度,成为即将运行的线程。
Windows是在调度后马上切换到选出的线程么?不一定,主动的调度和线程切换是连在一起的,调度完马上进行线程切换,但是“剥夺”式调度和线程切换是分离的,要等到CPU运行级别降到DISPATCH_LEVEL一下,才允许线程切换,因为线程的切换将导致堆栈的切换,而中断中是不允许堆栈切换的,可能这就是“剥夺”式调度和线程切换分离的原因吧。
线程切换时,如果切换前后的线程不属于同一个进程,还会引发一次进程切换,关于进程切换就不说了。
以上内容转载只:http://bbs.pediy.com/showthread.php?t=91419
关于线程调度消耗
1. 缓存失败?
内存访问的代价是相差很大的。在某种特定的RISC体系结构中,如果数据位于数据缓存中,那么数据访问需要一个CPU时钟周期;如果数据位于主存中(缓存失败),那么需要8个时钟周期;如果数据在磁盘中(页面错误),那么需要400,000个时钟周期,尽管确切的时钟周期数量可能不同,但不同的处理器体系结构在总的关系上是相同的;缓存成功、缓存失败和页面错误之间的速度(消耗CPU时钟)差别是不同数量级的差别。因此,楼主的问题有可能是多个线程处理数据时(而且每个线程对内存的访问非常频繁),产生了大量的缓存失败甚至是页面错误,这点可以通过性能监视器来分析。
2. 上下文切换?
上下文切换和缓存之间的交互对程序性能(消耗CPU时钟)的影响常常是最为有害的。结构和缓存类型对系统性能的这种特性产生显著的影响,缓存可以根据访问他们所使用的地址类型分成两类。虚拟编址缓存使用来自进程虚拟地址空间的地址访问。虚拟地址与进程无关。这就是说,系统中的每个进程都有可能使用虚拟地址100,但是虚拟地址100的物理定位将可能是几百个不同的物理地址。纯粹以来与虚拟地址的缓存在每次上下文切换时都需要刷新/失效。这将意味着每次上下文切换之后都需要重建缓存上下文。(最廉价的上下文重建需要至少100个时钟周期)
我想通过对上下文的简单阐述,大家还会说上下文的切换是很快的吗? 因为一个上下文切换至少要涉及缓存重建,它是非常昂贵的。
我针对楼主出现的现象,简单给出一个伪表达式,仅供参考:
1线程消耗的CPU时钟数 = 100000 CPU时钟数 + (上下文切换(按最小算) 100CPU时钟数 * 100次) = 110000
2线程消耗的CPU时钟数 = 100000 * 2 + (100CPU时钟数 * 200次) = 220000
3线程消耗的CPU时钟数 = 100000 * 3 + (100CPU时钟数 * 300次) = 330000
上面我的表达式是理想化的,大家可以想象一下,每次进行上下文切换时如果出现大量的缓存失败甚至是页面错误将会是什么结果。
WIN32靠线程的优先级(达到抢占式多任务的目的)及分配给线程的CPU时间来调度线程。WIN32本身的许多应用程序也利用了多线程的特性,如任务管理器等。
本质而言,一个处理器同一时刻只能执行一个线程( "微观串行 ")。WIN32多任务机制使得CPU好像在同时处理多个任务一样,实现了 "宏观并行 "。其多线程调度的机制为:
(1)运行一个线程,直到被中断或线程必须等待到某个资源可用;
(2)保存当前执行线程的描述表(上下文);
(3)装入下一执行线程的描述表(上下文);
(4)若存在等待被执行的线程,则重复上述过程。
应该是不一样的,我举一个例子你可能就明白了,例如,系统中运行了你的2个线程,其他的线程10个,这10个线程的代码中对内存的访问非常少,那么在这10个线程之间的上下文切换相对就是廉价的因为缓存失败或页面错误的机率就会低很多,但是当这些线程与你的线程进行上下文切换时就会存在
比较大的开销。
缓存的问题我现在觉得倒是值得我重点关注一下,那么,又有哪些需要注意的呢?是否,我将我的线程使用的所有内存都强制提交到物理内存,不允许被交换到磁盘上,这样对性能会有所改善呢?而对于CPU自身的高速缓存,我又能做些什么改善呢?
*******************************************************************************
个人观点:
1. 如你所说,提交到系统的物理内存是会改善页面错误的,
2. 在数据结构的设计上要使线程都要频繁访问的数据集中在连续的内存空间,例如:
你的线程中要使用两个全局类的两个实例
class Test
{
public:
int i;
char buffer[1024];
}
class Test1
{
public:
bool k;
int j;
}
假设你的线程重要频繁的访问 Test 中的 i 和 Test1中的 j ,而且往往是需要连续的访问
例如: TestObj.i++;
Test1Obj.j++;
那么我的建议是把 i 和 j 放到同一个 抽象的类中去使用,因为这样可以降低缓存失败的能性,当然这样做可能和 OO有些冲突,但我个人认为在性能关键的场合有时候是需要权衡的。
3. 就是策率了,例如缓式计算、延迟创建等等,都输入程序性能优化的方面(当然满足你的要求)。
主要问题集中在cache上,线程切换不会是主要问题。
一次cache失效的代价是几十到上百时钟周期,而线程切换代价是数千时钟周期,两者不在一个数量级上。并且两个解码线程是在两个核上跑,操作系统有线程亲缘性优化,这两个解码线程运行的核是不会被调换的,也就不会涉及线程上下文切换,至于其他非活动线程,被激活频率很低,可以忽略。
你的解码线程主要处理的是流数据,流数据的特点是要操作大量数据,这会对cache造成一定污染,处理不当会造成cache使用效率降低。但是CPU cache对程序员来说还是透明的,尽管SSE以后的指令集提供了几条cache操作指令,但是针对cache的优化手段目前还是比较有限的。存储系统的架构还有待进一步的改进。
另外还有一点忘了说,CPU占用率的测量并不是很准确的,有时候结果会有很大出入,比较精确的测量还是用CPU周期测量,建议用专门的profile工具或者自己插入profile代码来得到精确的测量。
该帖子详细请见:http://topic.csdn.net/t/20061231/21/5269296.html