C++语言并不支持多线程,C++的多线程编程是通过调用操作系统的低层函数实现的,常见的操作系统平台有MS的Windows、UNIX、LINUX、Open Solaris,C、C++都可以很好调用系统函数实现多线程。
采用多线程的好处大家都很熟悉了,可以充分利用系统资源,通过合理调度最大程序上并发执行,但是如果设计不当反而会与初衷相悖,带来更多的麻烦,本文主要就多线程编程中的“数据竞争”问题做一个归纳和总结,并给出WIN32下部分函数使用说明。
多线程编程中数据竞争是一项关键的技术,常用的解决方法有以下四种:临界区、互斥量、 事件 、 信号量
临界区一般不推荐使用,下面主要介绍后面三种。
一、 互斥量 Mutex
学过计算机网络的朋友相信对令牌环网应该不陌生,互斥量的作用就相当于一块令牌,每个主机都要竞争地去“申请”这张令牌,“获得”的主机才有权限在网上发数据包,而“令牌”只有一张,“令牌”的使用权只有当当前使用者“释放”后才能被其它主机“竞争申请”。
互斥量的特点是只有一个,各线程竞争使用,一个线程获得后,在它释放前,其它线程只好等待。
1. Win32平台下,互斥量为一个句柄,初始化方法如下:
Handle hMutex;
hMutex = CreateMutex(NULL, TRUE, NULL); //创建后当前线程初始占有互斥量
ReleaseMutex(hMutex); //创建后在主线程中释放互斥量从而子线程可以申请使用
hMutex = OpenMutex(MUTANT_ALL_ACCESS, TRUE, NULL); //打开互斥量,并声明子线程可以继承互斥量的句柄
2. 申请与释放
WaitForSingleObject(hMutex, DWORD dwTimeOut);
/* do the task; */
ReleaseMutex(hMutex);
例如,可设超时为100毫秒,如下所示:
if (WAIT_TIMEOUT == WaitForSingleObject(hrecvEven, 100)) {
/* do something; */ }
else {
/* do the task; */
ReleaseMutex(hMutex); }
二、事件 Event
事件常被大家比喻成为一个“红绿灯”,可以在线程中方便地把灯设为“红”从而阻塞部分请求该资源的线程,或设为“绿”,开启所有因这部分资源而阻塞的线程。
最常用的一个场景就是网络缓冲区,当数据处理线程从网络缓冲区中提取数据包进行处理时,首先要做的操作就是判断缓冲区是否为空,如非空则提取并处理,如为空则循环检测,这种实现会大大地把CPU资源浪费在循环检测,最好的方法是采用互斥事件,每次都用WaitForSingleObject去申请资源,如果为“红”时则线程阻塞,而写入缓冲区线程将数据写入时执行SetEvent函数,从而在整个进程空间中广播“绿”灯,这样处理线程状况就可以从阻塞变成就绪从而执行操作。
在使用互斥事件时常犯的一个错误就是误把事件当做互斥量在两个线程中防止数据竞争,如下例所示:
Handle hEvent;
hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // 实始化信号量,初始状态为非信号通知
SetEvent(hEvent ); //信号通知
ThreadA
{
WaitForSingleObject(hEvent);
ResetEvent(hEvent);
/* do the task; */
SetEvent(hEvnet);
}
ThreadB
{
WaitForSingleObject(hEvent);
ResetEvent(hEvent);
/* do the task; */
SetEvent(hEvnet);
}
上例中运行时会如何意想不到的事情,线程A执行时明明申请到了互斥事件并把灯设为“红”,但线程B还是可以申请到互斥事件并执行,原因是这样的,在A WaitForSingleObject成功后,在A执行ResetEvent之前,B可能抢占了CPU并执行了WaitForSingleObject,从而B也有权利执行ResetEvent,这样A、B都有权执行,这种情况下,等于有两个人都可以控制“红绿灯”从而导致“交通混乱”,最好的办法是在所有线程中只有一个线程可以开、关灯,或对互斥事件进行互斥量保护,防止数据竞争。
二、信号量 Semaphore
信号量的意义可以理解为代表一种资源的个数,比如是排队系统中座位的数量,所有它的值是大于或等于1的,等于1时信号量则退化为互斥量 Mutex。