多线程编程中需要知道的操作系统的概念

操作系统也曾看过几遍了,但始终未能透彻理解信号量、互斥、管程、消息传递等概念。

现在有时需要多线程编程,回头再看这些概念中体现的解决方案,有点感觉了。


多线程编程经典问题:

消费者和生产者共同使用一些资源问题(或读写一段共享内存问题),如何避免竞争条件(线程可能永远睡眠),死锁问题。


理解这些问题的前提假设: 

中断:一个线程刚执行一个操作(一个指令),就被中断(有可能是长时间中断、比如长时间不运行),其他线程开始运行。中断的类型有很多种,级别也不一样。

竞争条件:由中断产生,同一个资源被两个线程访问操作的问题,而且一个线程刚操作了一半,另外一个线程就抢断。

临界区:为避免竞争条件引起的问题,对共享资源、共享内存等进行保护,每次应该只运行一个线程运行的代码段。

互斥:对保护临界区、避免竞争条件的解决方法。

信号丢失:对一个线程发一个信号,如果该信号丢失,就再也找不回来了(?)


抛开忙等待的检测方法,直接使用sleep(),wakeup()来使线程阻塞(不被运行)、唤醒(开始运行)。

1. 首先采用flag的方式,或用count变量

两个线程共同使用一个变量,来表示这段资源是否可用。

应用sleep(),wakeup()和count的问题:

线程1consumer,读count==0,发现自己该sleep(), 但sleep()之前,被中断。

线程2producer开始运行,count=N,不能再生产,自己sleep()。

线程1consumer恢复中断,开始运行,直接进入sleep()

两个线程无人唤醒,就永远、幸福的sleep下去了。


2. 信号量

原子操作:变量检测、变量修改,以及可能的sleep()操作或wakeup()操作,是一个整体,不可分割、中断。


信号量可以用来实现同步(sychronizeatioin),保证事件顺序发生或不发生,而这个与互斥是不同的。

信号量是一个整型的计数器,当为0时,表示没有保存下来的唤醒操作;当为正值时,表示有一个或多个唤醒操作。

Down:原子操作:检测是否大于0,若大于0,则减1,继续;若等于0,则睡眠该进程,并且此线程的down操作并未完成(自己被阻塞,减1的操作并未完成)。

Up:原子操作:信号量增1,若有多个线程在该信号量上睡眠,则由系统选择其中一个(如随机),并允许该线程完成它自己的down操作。Up操作不会产生线程阻塞。


信号量是用于资源的同步,表示有多少资源可以使用。

而互斥量是用于每次只能有一个线程进入访问资源的代码段,防止混乱。


消费者、生产者问题,需要两个信号量和一个互斥量才能解决,且代码的逻辑要小心,否则会产生死锁。

如两个信号量full,empty(同一个资源,两个信号量),一个互斥量mutex(访问资源使用)

producer的操作:

DOWN(full)

DOWN(mutex)

produce;

UP(mutex)

UP(empty)


consumer的操作:

DOWN(empty)

DOWN(mutex)

consume;

UP(mutex)

UP(full)

对full,empty的信号量的操作,无需放入mutex内部,因为没有对资源访问。

empty,full两个信号量,相当于一个资源队列的  已用容量  和 未用容量,如produce时,必须先将未用容量减1,完成后,将已用容量加1,而不能反转次序。否则,如资源队列为空时,生产者先将empty加1,然后被抢断;consumer去消费,empty不为0,而资源队列里为0,就会混乱。

这个需要程序员来保证。不管怎么样,总算能解决资源共享的问题,并且相当于解决一个资源队列的问题。这个问题同样可以用一个无锁级别的方法来解决,可以对比一下,两者异曲同工,无锁队列采取的是producer只操作尾指针,且先插入数据,尾指针加1,用容量容量加1,consumer值操作头指针,直接头指针移动一下,head加1,相当于未用容量加1.  区别:无锁级别,一个线程只操作一个指针(变量),不会写两个变量。


3. 互斥量

用于管理共享资源或一小段代码,表示每次只运行一个线程访问,可以加锁,解锁mutex。

lock_mutex, unlock_mutex的实现,可以使用TSL或XCHG指令来实现。

mutex为0,表示可用,非0,不可用,重复测试,或时钟到,阻塞。


在使用信号量和互斥量时,必须小心,避免陷入死锁,两个线程同时被两个不同的信号量或互斥量阻塞。


4. 条件变量

条件变量的作用是允许线程因为一些条件达不到,而自己阻塞。然后等待的资源达到后,由另外一个线程唤醒它。

与互斥量的区别:互斥量是必须的,保证只有一个线程访问资源,可以对任意一个线程产生阻塞; 而条件变量是线程自身的条件达不到,而自行阻塞。

条件变量同信号量一样,可以与互斥量共同使用。不过条件变量,没有计数功能,仅集成了阻塞、唤醒功能,所以,需要程序员判断资源是否可用,访问资源,所以条件变量需要放入互斥量锁的内部使用。


以上,各概念变量基本上都是全局变量。各线程能共同访问。


5.  管程

一种更高级的同步原语,实现方式就是互斥、加条件变量,只不过,这些由编译器来完成,而无需程序员来保证无死锁、竞争等现象。

如采用1的实现方式,count,sleep(),wakeup(),只不过将count检测与sleep()或wakeup进行打包,不允许中断。

producer的一个produce过程、consumer的一个consume过程由编译器来负责其互斥。

仅少数几种语言支持管程,如java,而c、pascal是不支持管程的。


信号量和管程都无法解决一个或多CPU的(互斥问题),而无法解决分布式系统上的互斥问题,多个cpu,每个cpu都有自己的内存。


多线程编程中,其他使用的概念:

同步: 一个线程需要等待另外一个线程或资源

异步:无等待,多个线程同时执行

临界区域、临界区域变量(进入、退出临界区域的状态),有点类似于互斥量和锁。只不过,互斥量被锁时,进程被阻塞;而临界区域会忙测试,进行等待(busy loop),直到本线程的时间片耗完。 而且,系统无法清除死锁的临界区线程,而互斥量,系统可以处理死锁。

- 互斥器的时间开销远远大于临界区变量的使用,因为一个在usr mode下,一个是kernel mode下

- mutex互斥器可以跨进程使用,而临界区变量只能在同一个线程中使用

- mutex可以指定等待时间,而临界区变量不行

不要长时间锁住一片资源。

context switch:系统换了一个线程执行。

死锁:当一段代码需要两个或多个资源时,就存在死锁的危险。


消息传递: 解决进程间或多分布式间进程通信的方式。原语,是系统调用,而不是语言特性。有关分布式系统上的消息传递,还要遵守两次握手原则,防止消息丢失,有一个重发机制,和区分重复消息的机制(需要消息的序号)。与以上方式的区别,以上解决的问题,都是共享内存的。

同信号量的机制一样,只需将共享内存的N个槽,换成 消息缓冲区的N个槽就行了。

send:向目标发送消息,若消息缓冲区满,则本发送线程阻塞。

receive: 接收消息,若消息缓冲区空,则本接收进程阻塞。

信箱的的方式就是这种管理方式。若取消缓冲,就更直接明了了,send在receive之前,发送线程阻塞,直到receive发生;receive在send之后,接收线程阻塞,直到send发生。 


共享内存:IPC进程间通信的常用机制,速度更快。mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上。但若内存大小小于一个页面4KB,访问正常,若大于一个页面,则可能引发错误。

http://www.chinasb.org/archives/2012/03/4469.shtml

http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index1.html

http://baike.baidu.com/view/1499209.htm


事件event:受程序员完全控制的核心对象,不过在《现代操作系统》这本书里没提到。《win32多线程编程》里介绍的比较详细。createEvent是创建事件。用来同步对象。

setEvent:激活事件

resetEvent:重设事件

pulseEvent:若是manual方式,激活“所有”等待中线程,然后reset;若是auto方式,激活一个等待中线程,然后reset。

若无线程接收event,则event同样会丢失。

----------------------------------------------

如何在一个线程中控制另外一个线程?如终止

指定线程,首先需要的是该线程的handle

terminalThread(handle, exitCode):内核层面的结束,甚至无内存的清理,引起内存泄露。

Signals(信号),unix中有SIGKILL的函数,几乎等于terminalThread。

解决方法:

使用Event对象,然后让线程检测或等待它,条件满足时,线程自我结束。event可以取代全局变量。

所有线程可以共用一个事件event对象。

线程优先权的作用?大部分线程使用normal的优先权,轮流执行,只有必要时,采用其他的优先权。三部分:优先权类别、优先权层级,是用户可以设置的,优先权动态提升是操作系统的,三部分共同作用。

恢复一个线程 ?这个线程可以被再次执行。

resumeThread(handle) 

挂起一个线程?

suspendThread(handle)

---------------------------------------

还有一个比较紧迫的问题:

runtime library如何选择?

在VS的code generation中需要设置 runtime library:MT, MDT, MD, MDD

它的作用是 代码生成时,链接runtime library,但其有可能有不同的实现:

单线程静态版(vs2008,就没这个选项了,vc6.0以前ms还有的)

多线程静态版 vs 多线程dll版

 debug版 vs release版。来自《win32多线程编程》

所有的库链接时需要采用统一的runtime library链接方式,否则,有时就给出一堆的链接错误。

对此问题,网上已经有人的写的很清楚了:

Choosing the Correct C/C++ Runtime Library

http://www.davidlenihan.com/2008/01/choosing_the_correct_cc_runtim.html

  • Use the debug version only internally, release for anything that could be used by customers
  • Use the DLL version except in special situations
  • Use consistent settings throughout your project (the runtime library setting can be done per source file)
  • Don't worry if your runtime library settings match other libraries you use (unless the library comes in multiple runtime library versions)
以及,调用多个库,真的冲突了,需要忽略一些库:

若要使用此运行时库 请忽略这些库 
单线程 (libc.lib) libcmt.lib、msvcrt.lib、libcd.lib、libcmtd.lib、msvcrtd.lib 
多线程 (libcmt.lib) libc.lib、msvcrt.lib、libcd.lib、libcmtd.lib、msvcrtd.lib 
使用 DLL 的多线程 (msvcrt.lib) libc.lib、libcmt.lib、libcd.lib、libcmtd.lib、msvcrtd.lib 
调试单线程 (libcd.lib) libc.lib、libcmt.lib、msvcrt.lib、libcmtd.lib、msvcrtd.lib 
调试多线程 (libcmtd.lib) libc.lib、libcmt.lib、msvcrt.lib、libcd.lib、msvcrtd.lib 
使用 DLL 的调试多线程 (msvcrtd.lib) libc.lib、libcmt.lib、msvcrt.lib、libcd.lib、libcmtd.lib 

例如,如果收到此警告,并希望创建使用非调试、单线程版本的运行时库的可执行文件,可以将下列选项与链接器一起使用:
/NODEFAULTLIB:libcmt.lib /NODEFAULTLIB:msvcrt.lib /NODEFAULTLIB:libcd.lib /NODEFAULTLIB:libcmtd.lib /NODEFAULTLIB

详细的介绍在:

http://blog.csdn.net/monster_2495/article/details/7724597


----------------------------------------

编译链接时,对lib工程的preprocessor设置,仍要对主工程进行设置,否则,会出现链接 redefinition之类的错误。

----------------------------------------

两线程间传递数据,也有无锁的解决方法:采用队列的形式,读写互不干扰,下文里有详细的描述。

http://blog.csdn.net/viewcode/article/details/7937665


多线程编程中的概念与操作系统紧密相连,这里先总结下基本的。

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值