OS是如何实现多线程情况下的同步机制的。

Java,C#,C/C++等等的高级语言,无论是平台独立语言java/C#等,还是非独立的,如C/C++等,它们最终的代码都要有CPU和OS内核的进程和线程调度机构来执行的(对于java/C#这样的语言来说,很多人认为是在JVM/CLR这样的虚拟机上运行的,其实是有错误的,更加准确的说是,JVM/CLR提供了一种原语,比如bytecode/MSIL,当其编译时,由编译器将高级语法编译成bytecode/MSIL,然后JVM/CLR将这些高层的“汇编”翻译成二进制代码(JIT干的事情),这些二进制代码才是真正的可以执行的指令,这些指令时以线程的形式被调度给CPU执行的,所以根本上而言线程的问题和机能都是CPU和OS共同实现的事情,而不是什么java/C#等等它们自己实现的事情,只是提供了高层的表述,所以一旦理解CPU和OS是如何实现多线程条件下是如何实现同步机制的,那么也就找到了源头,对理解和使用java/C#等的多线程编程方式就比只停留在高级语言层面的理解要准确的多,甚至于可以自己开发更加高效的多线程例程库或者并发库,java 1.5以后增加的并发包是由一位教授实现的,其实也是在充分理解了OS这种实现,能够知道什么地方最优化,采用什么算法才能充分利用底层这种实现而设计和实现出来的。对个我们这些软件开发人员而言,有时间去了解一些底层的工作原理对从事的工作也是很有好处的,虽然我们直接用不上,但是我们常常间接的在用。

OS是这样做的,拿Window为例,由于多处理器、多核或者中断等(中断是调度线程的基本方式,当一个正在运行的线程被一个中断信号中断,那么CPU就会被其他线程接手,轮换的进行执行,CPU分给一个线程时间片类执行,因为CPU很快,所以人的眼睛看起来就像是多个任务同时执行似的)各种并发性因素存在,同样的代码可能被并发的执行,而数据也可能被并发的访问,这种情况下,比如一个共享的变量很可能就被一个中断而接手的线程修改覆盖其值,照成不一致的情况发生,这在金融或者在线交易型的一些系统表现明显,如果用户存入数据库中钱数被其他用户的线程给修改了,这是很可怕的事情!CPU和OS利用中断来调度线程时,是利用一种叫做IDT(中断描述表)的东西来实现的,IDT中将每一个中断和一个中断处理程序对应起来,也就是说一旦一个中断发生,OS就会去找对应的另外一个程序来处理,这样就停止了当前的线程而将CPU交给另外一个,OS在实现的时候自己也定义了一个软中断表,并且为中断定义了优先级0-31,其中0最低,0-2是软件可以使用的中断,其他是硬件中断,那么OS是这样使用优先级的,越高级的中断越不会被打断!那么OS如果提升当前线程的优先级,那么它就很不容易打断,此时可以保证一致性,但是由于调度器的存在,最终还是会被打断的,不可能长时间不给其他线程执行的机会!

所以提升中断优先级的作法只是一种一般作法,并不能从根本上解决问题。所以下面衍生出来很多其他的办法:

1)互锁操作:利用CPU的lock指令,锁住,也就是排除其他线程占有一个变量(变量进入寄存器后,CPU说了算),那么

这个变量就得到了同步保护!

2)自旋锁:提升其中断优先级,让它的执行体只是执行一个什么也不干的空循环,也就是它的状态总是等待的,那么CPU

也是空闲的,空转!其他线程也得不到它,那么可以解决调度器会自动降低优先级而使其他线程得到控制权的问题,那么一个线程处于等待它就不会去扰乱别人了,而且不会像第一种方法那么强势,所以很多非锁定算法使用了自旋锁的办法。

那么如何解除等待,又衍生出了很多有趣的东西,OS有一种等待块的数据结构,其实就是一个用来解除自旋状态的装置,它依靠信号才解除自旋,开和关的信号,关就是等待中,开就是解除等待,就像是交通的红绿灯,也就是说自旋状态必须使用信号显式的解除,高级语言都提供这样的东东,不过也都是OS底层实现的:

1)事件:一个事件的到来,而解除等待的状态

2)突变体:mutex,如果其是无信号的状态,那么线程就一定是占有它的,那么调度器就认为此线程应该自旋等待,否则就解除自旋等待状态,通常高级语言利用这个玩意实现“锁”的机制,这个锁不同于CPU的lock锁,是轻量级的,是程序可控的,就是通过程序代码发出信号来控制的,比如读写锁对象的lock和unlock方法,它们发布信号通知突变体改变其状态。一旦一个线程处于自旋等待,那么它就没有机会去修改一个变量的值,那么当前线程就不会因为被打断而变量被其他线程而修改覆盖了!这是同步机制实现的最基本办法!也是理解好什么事同步,什么是竞争条件,以及什么是线程安全概念的核心,这对理解好高级语言中的多线程问题很有帮助,也催生出各种数据结构来使用突变体,比如java 1.5并发包中的那些东西。

3)信号量:也就是semaphore,所以称量,也就是信号的数量,理解了突变体就很好理解什么是量!也就是给信号计数,

加或者是减,当计数到一个指定值时再发出一个开或者关的信号,那么其实就突变体的衍生和加强。

4)队列对象:一种数据结构,让线程在其中排队,排着队去占有一个变量或CPU时间

5)进程对象:当创建的时候是无信号的状态,当进程结束时是有信号的状态,这是很多程序的行为模式,比如调用一个程序时,只有等这个程序干完了活,自己才可以接着干自己的,这种情况司空见惯!但是还是突变体的用法创新而已。

6)线程对象:也是一种数据结构,这种情况也很平常,比如一段代码调用一个子程序或者子方法,必须等待子方法完成执行,自己才能接着执行。也就是创建线程的时候是无先好,线程销毁时是有信号!这是我们写程序是天天看到的东东。

7)定时器对象:计时器,没有到设定的时间时是无先好,到了时间就是有信号,看,也是突变体的变体!

其他就不介绍了,看,基本都是利用信号这种东东实现了这么多东西。从理解信号和突变体出发,就可以理解好其他的解决办法,理解好这些办法,就能更加灵活的使用那些高级语言提供的同步语法(比如java 1.5的并发包),甚至于发明更加好的同步和并发算法和实现。

所以研究OS内核的意义不在于我们去创造一个新内核,而是利用现有的原理更好的使用我们手中的工具!

猜你喜欢

转载自leospace.iteye.com/blog/921027
今日推荐