线程同步的方式主要有三种:互斥对象、事件对象和关键代码段。
一. 互斥对象
互斥对象(mutex)属于内核对象,它能够确保线程拥有对单个资源的互斥访问权。
互斥对象包含一个使用数量,一个线程ID和一个计数器。其中线程ID用于标识系统中的哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数。
创建互斥对象:调用函数CreateMutex。调用成功,该函数返回所创建的互斥对象的句柄。
请求互斥对象的所有权:调用函数WaitForSingleObject函数。线程必须主动请求共享对象的所有权才能获得该所有权。
释放指定互斥对象的所有权:调用ReleaseMutex函数。线程访问共享资源结束后,线程要主动释放对互斥对象的所有权,使该对象处于已通知状态。
二. 事件对象
事件对象也属于内核对象,它包含以下三个成员:
● 使用计数;
● 用于指明该事件是一个自动重置的事件还是一个人工重置的事件的布尔值;
● 用于指明该事件处于已通知状态还是未通知状态的布尔值。
事件对象有两种类型:人工重置的事件对象和自动重置的事件对象。这两种事件对象的区别在于当人工重置的事件对象得到通知时,等待该事件对象的所有线程均变为可调度线程;而当一个自动重置的事件对象得到通知时,等待该事件对象的线程中只有一个线程变为可调度线程。
1. 创建事件对象
调用CreateEvent函数创建或打开一个命名的或匿名的事件对象。
2. 设置事件对象状态
调用SetEvent函数把指定的事件对象设置为有信号状态。
3. 重置事件对象状态
调用ResetEvent函数把指定的事件对象设置为无信号状态。
4. 请求事件对象
线程通过调用WaitForSingleObject函数请求事件对象。
是
三. 关键代码段
关键代码段,也称为临界区,工作在用户方式下。它是指一个小代码段,在代码能够执行前,它必须独占对某些资源的访问权。通常把多线程中访问同一种资源的那部分代码当做关键代码段。
1. 初始化关键代码段
调用InitializeCriticalSection函数初始化一个关键代码段。
该函数只有一个指向CRITICAL_SECTION结构体的指针。在调用InitializeCriticalSection函数之前,首先需要构造一个CRITICAL_SCTION结构体类型的对象,然后将该对象的地址传递给InitializeCriticalSection函数。
2. 进入关键代码段
调用EnterCriticalSection函数,以获得指定的临界区对象的所有权,该函数等待指定的临界区对象的所有权,如果该所有权赋予了调用线程,则该函数就返回;否则该函数会一直等待,从而导致线程等待。
3. 退出关键代码段
线程使用完临界区所保护的资源之后,需要调用LeaveCriticalSection函数,释放指定的临界区对象的所有权。之后,其他想要获得该临界区对象所有权的线程就可以获得该所有权,从而进入关键代码段,访问保护的资源。
4. 删除临界区
当临界区不再需要时,可以调用DeleteCriticalSection函数释放该对象,该函数将释放一个没有被任何线程所拥有的临界区对象的所有资源。
四. 三者的比较
对于上面介绍的三种线程同步的方式,它们之间的区别如下所述:
● 互斥对象和事件都属于内核对象,利用内核对象进行线程同步时,速度较慢,但利用互斥对象和事件对象这样的内核对象,可以在多个线程中的各个线程间进行同步。
● 关键代码段工作在用户方式下,同步速度较快,但在使用关键代码段时,很容易进入死锁状态,因为在等待进入关键代码段时无法设定超时值。
通常,在编写多线程程序并需要实现线程同步时,首选关键代码段,由于它的使用比较简单,如果是在MFC程序中使用的话,可以在类的构造函数中调用InitializeCriticalSection函数,在该类的析构函数中调用DeleteCriticalSection函数,在所需保护的代码前面调用EnterCriticalSection函数,在访问完所需保护的资源后,调用LeaveCriticalSection函数。可见,关键代码段在使用上是非常方便的,但有几点需要注意:一是在程序中调用了EnterCriticalSection后,一定要相应的调用LeaveCriticalSection函数,否则其他等待该临界区对象所有权的线程将无法执行。二是如果访问关键代码段时,使用了多个临界区对象,就要注意防止线程死锁的发生。另外,如果需要在多个线程间的各个线程间实现同步的话,可以使用互斥对象和事件对象。
前提:假设有个经理,下面有5个项目组.经理同时最多能接5个项目,每个项目组一次只能做一个项目。
信标Semaphore:信标相当于,5个项目组都争先恐后的争夺项目。如果经理有5个项目,那么5个项目组都可以做。如果经理有3个项目,那就有2个项目没事情做,在等待。如果经理没有项目,那么5个组,都在闲着。
事件Event:相当于,外面有个项目,经理把项目接回来了。站在门口大喊:有项目了。喊完了,经理就不管了这个项目又没有人处理。
互斥Mutex:相当于,经理手上有1个项目。下面的组,主动地争夺项目,谁抢到谁就有事情做,没抢到的就得待着。
============================================================================================
形象的理解:
关键段与互斥量都有“线程所有权”概念,可以将“线程所有权”理解成旅馆的房卡,在旅馆前台登记名字拥有房卡后是可以多次进出房间的,其它人则无法进入直到你交出房卡。每个线程必须先通过EnterCriticalSection或WaitForSingleObject来尝试获得“线程所有权”才能调用LeaveCriticalSection或ReleaseMutex。否则会调用失败,这就相当于伪造房卡去办理退房手续——由于登记本上没有你的名字所以会被拒绝。 互斥量能很好的处理“遗弃”情况,因此在多进程之间可以放心的使用。
事件与信号量相当于管停车位的,信号量的大小相当于停车位容量多大,比如一共有5个停车位,来了3辆车,那可以直接进去,但是现在又来了3辆车,只能按先后顺序进去,有一辆车得等,直到停车场上有车开走了,这辆车才能进去。而事件相当于只有一个停车位的停车场。
1.线程(进程)同步的主要任务
答:在引入多线程后,由于线程执行的异步性,会给系统造成混乱,特别是在急用临界资源时,如多个线程急用同一台打印机,会使打印结果交织在一起,难于区分。当多个线程急用共享变量,表格,链表时,可能会导致数据处理出错,因此线程同步的主要任务是使并发执行的各线程之间能够有效的共享资源和相互合作,从而使程序的执行具有可再现性。
2.线程(进程)之间的制约关系?
当线程并发执行时,由于资源共享和线程协作,使用线程之间会存在以下两种制约关系。
(1).间接相互制约。一个系统中的多个线程必然要共享某种系统资源,如共享CPU,共享I/O设备,所谓间接相互制约即源于这种资源共享,打印机就是最好的例子,线程A在使用打印机时,其它线程都要等待。
(2).直接相互制约。这种制约主要是因为线程之间的合作,如有线程A将计算结果提供给线程B作进一步处理,那么线程B在线程A将数据送达之前都将处于阻塞状态。
间接相互制约可以称为互斥,直接相互制约可以称为同步,对于互斥可以这样理解,线程A和线程B互斥访问某个资源则它们之间就会产个顺序问题——要么线程A等待线程B操作完毕,要么线程B等待线程操作完毕,这其实就是线程的同步了。因此同步包括互斥,互斥其实是一种特殊的同步。
3.临界资源和临界区
在一段时间内只允许一个线程访问的资源就称为临界资源或独占资源,计算机中大多数物理设备,进程中的共享变量等待都是临界资源,它们要求被互斥的访问。每个进程中访问临界资源的代码称为临界区
看完概念性知识,下面用几个表格来帮助大家更好的记忆和运用多线程同步互斥的四个实现方法——关键段、事件、互斥量、信号量。
关键段CS与互斥量Mutex
| 创建或初始化 | 销毁 | 进入互斥区域 | 离开互斥区域 |
关键段CS | Initialize- CriticalSection | Delete- CriticalSection | Enter- CriticalSection | Leave- CriticalSection |
互斥量Mutex | CreateMutex | CloseHandle | 等待系列函数如WaitForSingleObject | ReleaseMutex |
关键段与互斥量都有“线程所有权”概念,可以将“线程所有权”理解成旅馆的房卡,在旅馆前台登记名字拥有房卡后是可以多次进出房间的,其它人则无法进入直到你交出房卡。每个线程必须先通过EnterCriticalSection或WaitForSingleObject来尝试获得“线程所有权”才能调用LeaveCriticalSection或ReleaseMutex。否则会调用失败,这就相当于伪造房卡去办理退房手续——由于登记本上没有你的名字所以会被拒绝。
互斥量能很好的处理“遗弃”情况,因此在多进程之间可以放心的使用。
事件Event
| 创建 | 销毁 | 使事件触发 | 使事件未触发 |
事件Event | CreateEvent | CloseHandle | SetEvent | ResetEvent |
注意事件的手动置位和自动置位要分清楚,不要混淆了。
信号量Semaphore
| 创建 | 销毁 | 递减计数 | 递增计数 |
信号量 Semaphore | Create- Semaphore | CloseHandle | 等待系列函数如WaitForSingleObject | Release- Semaphore |
信号量在计数大于0时表示触发状态,调用WaitForSingleObject不会阻塞,等于0表示未触发状态,调用WaitForSingleObject会阻塞直到有其它线程递增了计数。
注意:互斥量,事件,信号量都是内核对象,可以跨进程使用(通过OpenMutex,OpenEvent,OpenSemaphore)。
参考:http://blog.csdn.net/morewindows/article/details/7538247