线程间通信的两个基本问题是互斥和同步。
同步:一个线程的执行依赖于另一个线程的消息。
互斥:对共享资源的排他性,一个线程必须等待别的线程释放公共资源之后才能继续执行。
同步机制(Win32中):事件,信号量,互斥量,临界区
各种同步方式:
#全局变量
win32多线程通信的最方式,但用全局变量同步会有两个弊端,应该避免
>主线程没有进入休眠状态,依然会消耗CPU资源
>如果主线程优先级比ThreadFunc高,则全局变量无法在ThreadFunc中被改变,这样线程无法得到通知
#事件
由于event对象属于内核对象,则一个进程的线程可以控制另一进程中的线程运行
#临界区
只能用来同步本进程内的线程,而不可用来同步多个进程内的线程
#临界区的使用流程
>定义一个临界区结构变量
CRITICAL_SECTION g_cs;
>初始化临界区
InitializeCriticalSection( &g_cs );
>进入线程,在线程中进入临界区,离开临界区
UINT ThreadProc10 ( LPVOID pParam ) {
//进入临界区
EnterCriticalSection( &g_cs );
//开始使用共享资源
g_var = 100; // g_var 为全局共享资源
//离开临界区
LeaveCriticalSection( &g_cs );
#使用临界区注意事项
>每个共享资源使用一个CRITICAL_SECTION变量(临界区结构变量)
>不要长时间运行关键代码,每一个关键代码段长时间运行时,其他线程就会进入等待状态,这会降低应用程序的性能。
>如果需要同时访问多个资源,则可能连续调用EnterCriticalSection
>Critical Section不是OS核心对象,如果进入临界区的线程死掉,将无法释放资源
#互斥(Mutex)
Mutex是系统核心对象,可跨进程访问
>创建互斥量
HANDLE hMutex = CreateMutex ( .. LPCTSTR lpName )
>释放互斥量
BOOL WINAPI ReleaseMutex (HANDLE hMutex )
>使用互斥量的一般流程
void UpdateResource()
{
WaitForSingleObject (hMutex) ;
... // do somthing here
ReleaseMutex( hMutex );
}
#互斥与临界区的异同
>互斥对象的运行速度比临界区的关键代码慢
>不同进程中的多个线程能够访问单个互对象
>使用互斥时,线程在等待访问资源时可以设定一个超时值
#信号量的特点和用途
>信号量也可以通过名字跨进程使用
>如果当前资源的数量大于0,则信号量有效
>如果当前资源数量是0,则信号量无效
>系统决不允许当前资源的数量为负值
>当前资源数量绝不能大于最大资源量
#信号量相关函数
>创建信号量 CreateSemaphore ( .. PCTSTR pszName) ;
>释放信号量 ReleaseSemaphore ( .. )
>打开信号量 OpenSemaphore ( ... )
#互锁访问
使用方式 InterlockedExchangeAdd(&globalVar , 1); //可以让全局共享变量 globalVar + 1 以原子操作方式实现
>优点:互锁访问控制速度非常快
>缺点:资源类型只能是单一变量。如果资源比较复杂,仍然要使用临界区或互斥
#可等待定时器
可等待定时器是一个内核对象,在某个时间按某规定的时间间隔发出自己的信号通知的内核对象。
>创建可等待定时器
CreateWaitableTimer ( ... PCTSTR pszName--信号名称 )
>设置可等待定时器
SetWaitableTime( .. )
>取消可等待定时器
CancelWaitableTimer ( .. )
>打开可等待定时器
OpenWaitableTimer( .. )
#线程死锁的例子
主线程
辅线程 改进后的辅线程
此时程序会进入死锁而死掉, 两个线程互相等待对方释放临界区。