整理了一下Windows下线程同步互斥常用的几种方法,记录如下
1)CriticalSection: 临界区
适用范围 | 单一进程的各线程之间用来排它性占有 |
特 性 | 不是内核对象,快速而有效 |
相关结构 | CRITICAL_SECTION _critical |
相关方法 | /*初始化,最先调用的函数。没什么好说的,一般windows编程都有类似初始化的方法*/ InitializeCriticalSection(& _critical) /*释放资源,确定不使用_critical时调用,一般在程序退出的时候调用。如果以后还要用_critical,则要重新调用InitializeCriticalSection */ DeleteCriticalSection(& _critical) /* 把代码保护起来。调用此函数后,他以后的资源其他线程就不能访问了。 */ EnterCriticalSection(& _critical) /* 离开临界区,表示其他线程能够进来了。注意EnterCritical和LeaveCrticalSection必须是成对出现的! */ LeaveCriticalSection(& _critical) |
很好的封装:
class CritSect
{
public:
CritSect() { InitializeCriticalSection(&_critSection); }
~CritSect() { DeleteCriticalSection(&_critSection); }
void lock(){ EnterCriticalSection(&_critSection); }
void unlock(){ LeaveCriticalSection(&_critSection); }
private:
CRITICAL_SECTION _critSection;
};
class CritSectLock
{
public:
CritSectLock(CritSect& critSect):_critSect(critSect) { _critSect.lock(); }
~CritSectLock(){ _critSect.unlock(); }
private:
CritSect& _critSect;
};
#define HT_CS(crit) CritSectLock __critical_section(crit)
调用:CritSect sect; HT_CS(sect);
很好的封装:
2)Mutex: 互斥内核对象
适用范围 | 可以跨进程同步(创建命名互斥量),也可以用来做线程间的同步 |
特 性 | 核心对象,哪个线程拥有mutex,那么该mutex的ID和此线程的ID一样。 |
相关结构 | HANDLE hMutex; |
相关方法 | /* 创建互斥量,初始化的工作 参数一为安全选项,一般为NULL 参数二表示当前互斥量是否属于某个线程,一般为FALSE 参数三互斥量的名称,如果需要跨进程同步,则需要设置,其他情况一般为NULL。 */ CreateMutex(NULL,FALSE,NULL) //请求事件对象 WaitForSingleObject(hMutex,INIFINET); /* 释放互斥量,以使得其他线程可以访问。 */ ReleaseMutex(hMutex) /* 等待多个事件对象。参数nCount指定了要等待的内核对象的数目,存放这些内核对象的数组由lpHandles来指向。fWaitAll对指定的这nCount个内核对象的两种等待方式进行了指定,为TRUE时当所有对象都被通知时函数才会返回,为FALSE则只要其中任何一个得到通知就可以返回。dwMilliseconds在这里的作用与在WaitForSingleObject()中的作用是完全一致的。如果等待超时,函数将返回 WAIT_TIMEOUT。如果返回WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1中的某个值,则说明所有指定对象的状态均为已通知状态(当fWaitAll为TRUE时)或是用以减去WAIT_OBJECT_0而得到发生通知的对象的索引(当fWaitAll为FALSE 时) */ WaitForMultiObjects(DWORD nCount, // 等待句柄数 CONST HANDLE *lpHandles, // 句柄数组首地址 BOOL fWaitAll, // 等待标志 DWORD dwMilliseconds // 等待时间间隔) /* 打开一个已经创建好了的命名互斥量,用于跨进程同步 */ HANDLE OpenMutex( DWORD dwDesiredAccess, // 访问标志 BOOL bInheritHandle, // 继承标志 LPCTSTR lpName // 互斥对象名 ); |
class Mutex
{
public:
Mutex(char *pName = NULL)
{
m_mutex = CreateMutex(NULL, FALSE, pName);
assert(0 != m_mutex);
}
~Mutex()
{
BOOL ret = CloseHandle(m_mutex);
assert(TRUE == ret);
}
void lock() const
{
DWORD ret = WaitForSingleObject(m_mutex, INIFNITE);
assert(WAIT_OBJECT_0 == ret);
}
void unlock() const
{
BOOL ret = ReleaseMutex(m_mutex);
assert(TRUE == ret);
}
private:
HANDLE m_mutex;
};
class MutexLock
{
public:
MutexLock(Mutex& mx):mutex(mx) { mutex.lock(); }
~MutexLock(){ mutex.unlock(); }
private:
Mutex& mutex;
};
#define HT_MX(mx) MutexLock __mutex__(mx)
3)Event: 事件内核对象
适用范围 | 多用于线程间的通信,可以跨进程同步(创建命名的事件对象)。 |
特 性 | 核心对象,有两种不同类型的事件对象。一种是人工重置的事件,另一种是自动重置的事件。当人工重置的事件得到通知时(signaled),等待该事件的所有线程均变为可调度线程。当一个自动重置的事件得到通知时,等待该事件的线程中只有一个线程变为可调度线程。 |
相关结构 | HANDLE hEvent; |
相关方法 | /* 初始化方法,创建一个事件对象,第一个参数表示安全属性,一般情况下,遇到这类型的参数直接给空就行了,第二个参数是否是人工重置。(内核事件有两种工作模式:人工重置和自动重置。其区别会在下面提到。)。第三个参数是初始状态,第四个参数事件名称。 */ hEvent = CreateEvent(NULL,FALSE,FALSE,NULL); /* 等待单个事件置位,即线程会在这个函数阻塞直到事件被置位,SetEvent。 如果是自动重置事件,则在此函数返返回后系统会自动调用ResetEvent(hEvnet),重置事件,保证其他线程不能访问。 如果是人工重置事件,则在此函数返回以后,系统的其他线程能继续访问。 第二个参数说明等待时间,INIFINET表示一直等待。 */ WatiForSingleObject(hEvent,INIFINET) /* 置位事件,只有使事件置位(有信号状态),线程才能进去访问。即WatiForSingleObject(hEvent,INIFINET)才返回 */ SetEvent(hEvent); /* 重置事件,使事件变为无信号状态 */ ResetEvent(hEvent) /* 等待多个事件对象。参数nCount指定了要等待的内核对象的数目,存放这些内核对象的数组由lpHandles来指向。fWaitAll对指定的这nCount个内核对象的两种等待方式进行了指定,为TRUE时当所有对象都被通知时函数才会返回,为FALSE则只要其中任何一个得到通知就可以返回。dwMilliseconds在这里的作用与在WaitForSingleObject()中的作用是完全一致的。如果等待超时,函数将返回 WAIT_TIMEOUT。如果返回WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1中的某个值,则说明所有指定对象的状态均为已通知状态(当fWaitAll为TRUE时)或是用以减去WAIT_OBJECT_0而得到发生通知的对象的索引(当fWaitAll为FALSE 时) */ WaitForMultiObjects(DWORD nCount, // 等待句柄数 CONST HANDLE *lpHandles, // 句柄数组首地址 BOOL fWaitAll, // 等待标志 DWORD dwMilliseconds // 等待时间间隔) /* 打开一个命名的事件对象,可以用来跨进程同步 */ HANDLE OpenEvent( DWORD dwDesiredAccess, // 访问标志 BOOL bInheritHandle, // 继承标志 LPCTSTR lpName // 指向事件对象名的指针 ); |
很好的封装,用于条件互斥
#ifndef ETIMEDOUT
#define ETIMEDOUT WAIT_TIMEOUT
#endif
class CondMutex
{
public:
CondMutex()
{
m_cond = CreateEvent(NULL, FALSE, FALSE, NULL);
assert(0 != m_cond);
}
~CondMutex()
{
BOOL ret = CloseHandle(m_cond);
assert(TRUE == ret);
}
int wait_with_timeout(Mutex *mutex, int mill) const
{
mutex->unlock();
DWORD ret = WaitForSingleObject(m_cond, mill);
mutex->lock();
if (WAIT_OBJECT_0 == ret)
{
return 0;
}
else if (WAIT_TIMEOUT == ret)
{
return ETIMEDOUT;
}
assert(0);
return ret;
}
int wait(Mutex *mutex) const
{
mutex->unlock();
DWORD ret = WaitForSingleObject(m_cond, INIFINET);
mutex->lock();
if (WAIT_OBJECT_0 == ret)
{
return 0;
}
assert(0);
return ret;
}
void signal() const
{
SetEvent(m_cond);
}
private:
HANDLE m_cond;
};
调用
Mutex mutex;CondMutex condMutex;
{
HT_MT(mutex);
int ret = condMutex.wait(&mutex);
if (0 == ret)
{ // 成功
......
}
}
4)Semaphore: 信号内核对象
适用范围 | 用来限制资源占用 |
特 性 | 核心对象,没有拥有者,任何线程都可释放。信号量(Semaphore)内核对象对线程的同步方式与前面几种方法不同,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用 CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过 ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。 |
相关结构 | HANDLE hSemaphore; |
相关方法 | /* 初始化方法,创建一个事件对象,第一个参数表示安全属性,一般情况下,遇到这类型的参数直接给空就行了,第二个参数是开始时可供使用的资源数,第三个参数是最大资源数,第四个参数是信号名称。 */ hSemaphore = CreateSemaphore(NULL, 1, 1, NULL) /* 等待信号量,只要信号量的可用资源数>0,则会返回,并将可用资源数-1 第二个参数说明等待时间,INIFINET表示一直等待。 */ WaitForSingleObject(hSemaphore, INFINITE); /* 对指定的信号量增加指定的值。 第一个参数为信号量句柄,第二个参数为需要增加的数量,第三个参数用于获取信号量上次的值 */ ReleaseSemaphore(hSemaphore, 1, NULL); /* 为现有的一个已命名信号量对象创建一个新句柄。 dwDesiredAccess Integer,下述常数之一: SEMAPHORE_ALL_ACCESS (0x1F0003) 要求对事件对象的完全访问; SEMAPHORE_MODIFY_STATE (0x0002) 允许使用ReleaseSemaphore函数; SYNCHRONIZE (0x00100000L)允许同步使用信号机对象。 bInheritHandle Integer,如果允许子进程继承句柄,则设为TRUE。 lpName String,指定要打开的对象的名字。 */ HANDLE OpenSemaphore( DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName ); |