关键段结构
typedef struct _RTL_CRITICAL_SECTION {
PRTL_CRITICAL_SECTION_DEBUGDebugInfo;
LONG LockCount;
LONG RecursionCount;
HANDLE OwningThread; // 这个最关键,理解了这个变量就理解了关键段
HANDLE LockSemaphore;
DWORD SpinCount;
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
关键段中有个线程所有权的概念,
简单来说就是调用 EnterCriticalSection 的线程, 自身必须调用 LeaveCriticalSection 才会释放. Enter 与Leave 要配对使用.
如若在别的线程调用 LeaveCriticalSection 是无效的;
同时需要注意 EnterCriticalSection 可以在同一线程中调用 RecursionCount 次 , 那么 LeaveCriticalSection 也需要调用相同次数.
否则死锁;
注意:
关键段与读写锁 都是用于线程间的互斥, 但无法用于线程同步 , 线程同步需要用到内核对象,比如event对象.
比如关键段只能在同一线程中使用Enter , Leave. 而无法在一个线程中使用Enter , 另一个使用Leave;
代码说明:
int g_count = 0;
//2个关键段
CRITICAL_SECTION cs_lock_param,cs_lock_thread;
unsigned int WINAPI t1( void* lpParameter)
{
// 应在同一线程中调用 Enter , Leave
EnterCriticalSection(&cs_lock_param);
int param = *(int*)lpParameter;
LeaveCriticalSection(&cs_lock_param);
EnterCriticalSection(&cs_lock_thread);
// EnterCriticalSection(&cs_lock_thread); 此处取消注释将死锁.Enter , Leave 注意配对使用
g_count++;
cout <<"tid:" << GetCurrentThreadId() << "," <<g_count << "," << param<< endl;
LeaveCriticalSection(&cs_lock_thread);
return 0;
}
int main()
{
/*
初始化关键段, 也可以使用InitializeCriticalSection
InitializeCriticalSectionAndSpinCount 在多核处理器上或许速度更快些,使用旋转锁
先循环尝试进入,如果超时, 则切换成内核等待状态
*/
InitializeCriticalSectionAndSpinCount(&cs_lock_param,4000); // 4000 是循环次数.
InitializeCriticalSectionAndSpinCount(&cs_lock_thread,4000);
HANDLE handles[5];
for(int i = 0; i < 5; ++i) {
/*
注意不能在这里EnterCriticalSection;
即在另一个线程中 Leave 是无效的
*/
//EnterCriticalSection(&cs_lock_param);
handles[i] = (HANDLE)_beginthreadex(NULL,0,t1,&i,0,NULL);
}
WaitForMultipleObjects(5,handles,TRUE,INFINITE);
DeleteCriticalSection(&cs_lock_param);
DeleteCriticalSection(&cs_lock_thread);
for(int i = 0; i < 5 ; ++i)
CloseHandle(handles[i]);
return 0;
}
读写锁:
读锁: 多个线程可同时访问.
AcquireSRWLockShared 可同时进入.
对应的读线程很适合用于读取共享资源
写锁:与关键段类似,如果愿意, 你也可以把关键段替换成 写锁 ,
AcquireSRWLockExclusive, 同一时间只有一个线程可以获取此锁
用写锁模拟关键段:
unsigned int WINAPI write_thread( void* lpParameter)
{
//获取写锁
AcquireSRWLockExclusive(&srwlock);
g_count++;
cout << "tid:" << GetCurrentThreadId() << " writing" << endl;
Sleep(3000);
cout << "tid:" << GetCurrentThreadId() << " writing end , gcount:" << g_count << endl;
cout << flush;
//释放
ReleaseSRWLockExclusive(&srwlock); //如果注释则将死锁
return 0;
}
int main()
{
//初始化
InitializeSRWLock(&srwlock);
HANDLE handles[5];
for(int i = 0; i < 5 ; ++i)
handles[i] = (HANDLE)_beginthreadex(0,0,write_thread,0,0,0);
WaitForMultipleObjects(5,handles,TRUE,-1);
for(int i = 0; i < 5;++i)
CloseHandle(handles[i]);
return 0;
}
读写锁:
volatile int g_count = 0;
SRWLOCK srwlock;
//读线程
unsigned int WINAPI read_thread( void* lpParameter)
{
AcquireSRWLockShared(&srwlock);
cout << "tid:" << GetCurrentThreadId() << " reading , gcount:" << g_count << endl;
Sleep(1000);
ReleaseSRWLockShared(&srwlock);
return 0;
}
//写线程
unsigned int WINAPI write_thread( void* lpParameter)
{
AcquireSRWLockExclusive(&srwlock);
g_count++;
cout << "tid:" << GetCurrentThreadId() << " writing" << endl;
Sleep(500);
cout << "tid:" << GetCurrentThreadId() << " writing end , gcount:" << g_count << endl;
cout << flush;
ReleaseSRWLockExclusive(&srwlock);
return 0;
}
int main()
{
//初始化
InitializeSRWLock(&srwlock);
//5个写线程, 10个读
HANDLE handles_w[5] , handles_r[10];
//3个读锁
for(int i = 0; i < 3; ++i)
handles_r[i] = (HANDLE)_beginthreadex(0,0,read_thread,0,0,0);
//5个写锁
for(int i = 0; i < 5 ; ++i)
handles_w[i] = (HANDLE)_beginthreadex(0,0,write_thread,0,0,0);
//7个读锁
for(int i = 3; i < 10; ++i)
handles_r[i] = (HANDLE)_beginthreadex(0,0,read_thread,0,0,0);
WaitForMultipleObjects(5,handles_w,TRUE,-1);
WaitForMultipleObjects(10,handles_r,TRUE,-1);
for(int i = 0; i < 10;++i)
{
if(i<5)
CloseHandle(handles_w[i]);
CloseHandle(handles_r[i]);
}
return 0;
}
事件:
是一个内核对象, 意味着可以跨线程/进程 调用;
最简单的一个例子:
/*
第一和第四个参数不说明了;
先看第三个参数, 如果把其设置成TRUE .
相当于 调用了SetEvent , 即触发状态.
此时 WaitForSingleObject 将直接返回
*/
HANDLE e = CreateEvent(0,FALSE,TRUE,0);
WaitForSingleObject(e,-1);
cout << "end" << endl;
第一个例: 手动档Event . 简介
从下面的代码中可以知, 不像关键段必须在同一个线程中使用
HANDLE ge;
//3个线程
unsigned int WINAPI event_thread1( void* lpParameter)
{
WaitForSingleObject(ge,-1);
cout << "event_thread1" << endl;
// SetEvent(ge); 如果CreateEvent 第2个参数是FALSE;
return 0;
}
unsigned int WINAPI event_thread2( void* lpParameter)
{
WaitForSingleObject(ge,-1);
cout << "event_thread2" << endl;
// SetEvent(ge); 如果CreateEvent 第2个参数是FALSE;
return 0;
}
unsigned int WINAPI event_thread3( void* lpParameter)
{
WaitForSingleObject(ge,-1);
cout << "event_thread3" << endl;
// SetEvent(ge); 如果CreateEvent 第2个参数是FALSE;
return 0;
}
int main()
{
/*
CreateEvent(NULL,TRUE,FALSE,NULL);
重点说第二个参数, 其他参数参msdn;
如果是 TRUE 意味着手动档, 即如果想重新使用 Event对象需要调用 ResetEvent;
在3线程中 WaitForSingleObject 之后都是触发状态,所以将一起执行.
就像跑100米, 裁判打枪(SetEvent) , 3个线程一起跑;
如果是FALSE,WaitForSingleObject 之后将自动调用ResetEvent
所以, 只有其中一个线程将运行, 此时 Event是非触发状态. 如果需要继续
其他线程则需要重新调用SetEvent .
*/
ge = CreateEvent(NULL,TRUE,FALSE,NULL);
HANDLE handles[3];
handles[0] = (HANDLE)_beginthreadex(0,0,event_thread1,0,0,0);
handles[1] = (HANDLE)_beginthreadex(0,0,event_thread2,0,0,0);
handles[2] = (HANDLE)_beginthreadex(0,0,event_thread3,0,0,0);
//触发对象. 意思是启动吧,我的哥;
SetEvent(ge);
WaitForMultipleObjects(3,handles,TRUE,-1);
return 0;
}
第二个例子:自动档 . 此例子解决同步问题
HANDLE hEvent;
unsigned int __stdcall fun_event(void* p)
{
int n = *(int*)p;
printf("in thread :%d\n",n);
SetEvent(hEvent);
return 0;
}
int main()
{
/*
由于第二个参数是自动重置, 意味WaitForSingleObject返回后由
系统调用ResetEvent;
如果第二个参数是TRUE , 以下的代码将有问题.
问题在于 WaitForSingleObject 只将等待一次,
即第一个线程调用SetEvent 后, WaitForSingleObject将不会Reset.
此时Event对象, 一直处于触发状态.
所以除了第一个线程创建之后将等待, 其他线程将直接创建,WaitForSingleObject 将直接返回;
如果想让代码有效则需要在 WaitForSingleObject 之后 ResetEvent
*/
hEvent = CreateEvent(NULL,FALSE,FALSE, NULL);
HANDLE handles[5];
for(int i = 0; i < 5; ++i)
{
printf("in main :%d\n",i);
handles[i] = (HANDLE)_beginthreadex(0,0,fun_event,&i,0,0);
WaitForSingleObject(hEvent,-1);
// ResetEvent(hEvent); 如果 CreateEvent(NULL,TRUE,FALSE, NULL); 则需要自己Reset
}
WaitForMultipleObjects(5,handles,TRUE,-1);
CloseHandle(hEvent);
return 0;
}
补充一个 : PulseEvent ,例子不写了, 相当于先Set , 后Reset. 一直没在实际工程中用过;
互斥量 与关键段类似 , 一个内核状态,一个用户状态. 都有线程所有权.
关键段使用 EnterCriticalSection 来解锁.
互斥量 使用 WaitForSingleObject 来解锁. 一旦等待成功则 互斥量 内部将记录此线程id.
WaitForSingleObject 之后 线程成为了此 mutex 的拥有者 即独占了资源的访问权
, 在使用完后需要ReleaseMutex;
如果忘记了ReleaseMutex 对于互斥量来说 是一个遗弃状态.
对于关键段 来说 如果忘记了 Leave 则直接死锁.
而互斥量 是一个内核对象 , 即可以跨进程/线程使用 . 一旦忘记ReleaseMutex ,系统将调整这种状态, 即修改mutex
内部的变量,全部清除 . 好让其他 WaitForSingleObject 的进程/线程 继续;
例子:
HANDLE mutex;
unsigned int WINAPI mutex_thread( void* lpParameter)
{
DWORD r = WaitForSingleObject(mutex,-1); //线程争取中... 哪个被系统分配到了就进去
cout << "ret:" << r <<endl;
Sleep(1000);
ReleaseMutex(mutex);
return 0;
}
int main()
{
mutex = CreateMutex(NULL,FALSE,NULL); //注意第2个参数 , 如果是TRUE , 则需在主线程中先释放
HANDLE arr[3];
for(int i = 0; i < 3; ++i){
arr[i] = (HANDLE)_beginthreadex(0,0,mutex_thread,0,0,0);
}
//ReleaseMutex(mutex); 如果CreateMutex(NULL,TRUE,NULL)
WaitForMultipleObjects(3,arr,TRUE,-1);
return 0;
}
遗弃状态 : 意思是当需要 ReleaseMutex 的线程,因为某些原因崩溃了 . WaitForSingleObject 会返回 WAIT_ABANDONED
对上面的例子进行修改. 成为遗弃状态的mutex;
HANDLE mutex;
unsigned int WINAPI mutex_thread( void* lpParameter)
{
DWORD r = WaitForSingleObject(mutex,-1);
cout << "ret:" << r <<endl; //看返回值,第2,3个线程都是128
Sleep(1000);
/*
除了第一个抢占到的线程返回了 0 . 其他都是128;
原因是第一个抢占的线程并没有 ReleaseMutex .
此时系统将清除 mutex 内部的状态 . 让后续的线程继续抢占.
*/
// ReleaseMutex(mutex); 注释掉这一行
return 0;
}
//main中无变化.
int main()
{
mutex = CreateMutex(NULL,FALSE,NULL);
HANDLE arr[3];
for(int i = 0; i < 3; ++i){
arr[i] = (HANDLE)_beginthreadex(0,0,mutex_thread,0,0,0);
}
WaitForMultipleObjects(3,arr,TRUE,-1);
return 0;
}
信号量:
可以把Semaphore看成一个计数器 , 增加1 , 则有一个Wait 的线程可以启动;
常常用于线程的控制, 比如 最多同时启动3个线程, 当然也可以模拟事件对象 类似的同步功能
CreateSemaphore 第2个参数是当前用数, 第3个参数是最多数量
如果第2个是 0 , 第3个是 3 . 此时如果A线程正在 WaitForSingleObject, 将等待. 直到B线程
ReleaseSemaphore(hSem,1,NULL); 则给信号量增加1个可用数, 那么A线程将可调度了;
死锁的例子:
由于起始可用数为 0 , 同时没有一个线程ReleaseSemaphore ,将产生死锁. 注意
Semaphore 没有遗弃的问题
HANDLE sem;
unsigned int WINAPI sem_thread( void* lpParameter)
{
WaitForSingleObject(sem,-1); //等待信号量.
cout << GetCurrentThreadId() << endl;
return 0;
}
int main()
{
sem = CreateSemaphore(NULL,0,3,NULL); //起始可以用数: 0 , 最大可用数 3.
HANDLE arr[3];
for(int i = 0; i < 3; ++i){
arr[i] = (HANDLE)_beginthreadex(0,0,sem_thread,0,0,0);
}
WaitForMultipleObjects(3,arr,TRUE,-1);
return 0;
}
修改上面的代码:
HANDLE sem;
unsigned int WINAPI sem_thread( void* lpParameter)
{
WaitForSingleObject(sem,-1); //等待信号量.
cout << GetCurrentThreadId() << endl;
ReleaseSemaphore(sem,1,NULL); //让信号量加1.
return 0;
}
int main()
{
sem = CreateSemaphore(NULL,0,3,NULL); //起始可以用数: 0 , 最大可用数 3.
HANDLE arr[3];
for(int i = 0; i < 3; ++i){
arr[i] = (HANDLE)_beginthreadex(0,0,sem_thread,0,0,0);
}
//过了3秒 , 增加1个可用数;
Sleep(3000);
//调用后 , 将有1个线程抢占到;
ReleaseSemaphore(sem,1,NULL);
WaitForMultipleObjects(3,arr,TRUE,-1);
return 0;
}
Sem模拟事件:
HANDLE sem;
unsigned int WINAPI sem_thread( void* lpParameter)
{
long count = 0;
BOOL ret = ReleaseSemaphore(sem,1,&count); //相当于SetEvent
cout << "ret :" << ret << ", precount:" << count << endl;
return 0;
}
int main(int argc, char *argv[])
{
//模拟事件.
sem = CreateSemaphore(NULL,0,1,NULL); //起始可以用数: 0 , 最大可用数 1;
HANDLE arr[3];
for(int i = 0; i < 3; ++i){
arr[i] = (HANDLE)_beginthreadex(0,0,sem_thread,0,0,0);
//此时将等待, 直到ReleaseSemaphore , 与事件类似;
WaitForSingleObject(sem,-1);
}
WaitForMultipleObjects(3,arr,TRUE,-1);
return 0;
}