互斥锁、临界区和事件
一.互斥锁的介绍
互斥锁用于控制多个线程对他们之间共享资源互斥访问的一个信号量。也就是说是为了避免多个线程在某一时刻同时操作一个共享资源。
例如线程池中的有多个空闲线程和一个任务队列。
任何是一个线程都要使用互斥锁互斥访问任务队列,以避免多个线程同时访问任务队列以发生错乱。
在某一时刻,只有一个线程可以获取互斥锁,在释放互斥锁之前其他线程都不能获取该互斥锁。
如果其他线程想要获取这个互斥锁,那么这个线程只能以阻塞方式进行等待。
这就是像厕所只有一个坑,必须一个一个上的原理。
win32实现一个互斥锁:
#include "stdafx.h"
#include<Windows.h>
#include<iostream>
using namespace std;
//互斥锁
HANDLE hMutex1;
int flag;
DWORD WINAPI MyThread2(LPVOID lpParamter)
{
while (1)
{
//没上锁的话就自己锁上,否则等着
WaitForSingleObject(hMutex1,INFINITE);
flag=!flag;
cout << "MyThread1 Runing :"<<"线程2"<<" "<<flag<< endl;
Sleep(1000);
//解锁
ReleaseMutex(hMutex1);
}
}
DWORD WINAPI MyThread1(LPVOID lpParamter)
{
while (1)
{
//没上锁的话就自己锁上,否则等着
WaitForSingleObject(hMutex1,INFINITE);
flag=!flag;
cout << "MyThread2 Runing"<<"线程1" <<" "<<flag<< endl;
Sleep(10);
//解锁
ReleaseMutex(hMutex1);
}
}
int _tmain()
{
//创建一个锁
hMutex1 =CreateMutex(NULL,FALSE,NULL);
HANDLE hThread1 = CreateThread(NULL, 0, MyThread1, NULL, 0, NULL);
CloseHandle(hThread1);
HANDLE hThread2 = CreateThread(NULL, 0, MyThread2, NULL, 0, NULL);
CloseHandle(hThread2);
while(1);
return 0;
}
二.临界区介绍
指的是一个访问共用资源的程序片段,而这些共用资源又无法同时被多个线程访问的特性。
当有线程进入临界区段时,其他线程或是进程必须等待,有一些同步的机制必须在临界区段的进入点与离开点实现,
以确保这些共用资源是被互斥获得使用。其实跟互斥锁差不多.
重点介绍临界区
InitializeCriticalSection 初始化临界区
DeleteCriticalSection 删除临界区
EnterCriticalSection 进入临界区
LeaveCriticalSection 离开临界区
win32例子:
#include "stdafx.h"
#include<Windows.h>
#include<iostream>
using namespace std;
int flag;
typedef CRITICAL_SECTION UPF_thread_mutex_t;
class CGfxMutex
{
public:
CGfxMutex()
{
::InitializeCriticalSection(&lock_);
}
~CGfxMutex()
{
::DeleteCriticalSection(&lock_);
}
void Lock()
{
::EnterCriticalSection(&lock_);
}
void Unlock()
{
::LeaveCriticalSection(&lock_);
}
// 临界区结构对象
UPF_thread_mutex_t lock_;
};
CGfxMutex *mutex;
DWORD WINAPI MyThread2(LPVOID lpParamter)
{
while (1)
{
mutex->Lock();
flag=!flag;
cout << "MyThread1 Runing :"<<"线程2"<<" "<<flag<< endl;
Sleep(900);
mutex->Unlock();
}
};
DWORD WINAPI MyThread1(LPVOID lpParamter)
{
while (1)
{
mutex->Lock();
flag=!flag;
cout << "MyThread2 Runing"<<"线程1" <<" "<<flag<< endl;
Sleep(1000);
mutex->Unlock();
}
};
void main()
{
flag = 1;
//创建一个锁
mutex = new CGfxMutex;
HANDLE hThread1 = CreateThread(NULL, 0, MyThread1, NULL, 0, NULL);
CloseHandle(hThread1);
HANDLE hThread2 = CreateThread(NULL, 0, MyThread2, NULL, 0, NULL);
CloseHandle(hThread2);
while(1);
return;
}
临界区和互斥锁的区别:
1、临界区只能用于对象在同一进程里线程间的互斥访问;互斥体可以用于对象进程间或线程间的互斥访问。
2、临界区是非内核对象,只在用户态进行锁操作,速度快;互斥体是内核对象,在核心态进行锁操作,速度慢。
3、临界区和互斥体在Windows平台都下可用;Linux下只有互斥体可用
windows平台:
InitializeCriticalSection 初始化临界区
DeleteCriticalSection 删除临界区
EnterCriticalSection 进入临界区
LeaveCriticalSection 离开临界区
CreateMutex 创建锁
WaitForSingleObject 没上锁的话就自己锁上,否则等着
ReleaseMutex 释放锁
CloseHandle 销毁锁
linux平台(linux平台使用的是互斥锁,没有临界区)
创建 pthread_mutex_init
销毁 pthread_mutex_destroy
加锁 pthread_mutex_lock
解锁 pthread_mutex_unlock
三.关于条件的使用
API:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTESl pEventAttributes,// 安全属性
BOOL bManualReset,// 复位方式
BOOL bInitialState,// 初始状态
LPCTSTRl pName // 对象名称
);
pEventAttributes:
一个指向SECURITY_ATTRIBUTES结构的指针,确定返回的句柄是否可被子进程继承。如果lpEventAttributes是NULL,此句柄不能被继承。
Windows NT/2000:lpEventAttributes的结构中的成员为新的事件指定了一个安全符。如果lpEventAttributes是NULL,事件将获得一个默认的安全符。
bManualReset:
指定将事件对象创建成手动复原还是自动复原。如果是TRUE,那么必须用ResetEvent函数来手工将事件的状态复原到无信号状态。
如果设置为FALSE,当一个等待线程被释放以后,系统将会自动将事件状态复原为无信号状态。
bInitialState[输入]
指定事件对象的初始状态。如果为TRUE,初始状态为有信号状态;否则为无信号状态。
pName
指定事件的对象的名称,是一个以0结束的字符串指针。名称的字符格式限定在MAX_PATH之内。名字是对大小写敏感的。
如果lpName指定的名字,与一个存在的命名的事件对象的名称相同,函数将请求EVENT_ALL_ACCESS来访问存在的对象。
这时候,由于bManualReset和bInitialState参数已经在创建事件的进程中设置,这两个参数将被忽略。
如果lpEventAttributes是参数不是NULL,它将确定此句柄是否可以被继承,但是其安全描述符成员将被忽略。
如果lpName为NULL,将创建一个无名的事件对象。
如果lpName的和一个存在的信号、互斥、等待计时器、作业或者是文件映射对象名称相同,函数将会失败,
在GetLastError函数中将返回ERROR_INVALID_HANDLE。造成这种现象的原因是这些对象共享同一个命名空间。
终端服务(Terminal Services):名称中可以加入"Global"或是"Local"的前缀,这样可以明确的将对象创建在全局的或事务的命名空间。
名称的其它部分除了反斜杠(\),可以使用任意字符。
BOOL SetEvent(HANDLE hEvent);
设置事件的状态为有标记,释放任意等待线程。如果事件是手工的,此事件将保持有标记直到调用ResetEvent,这种情况下将释放多个线程;
如果事件是自动的,此事件将保持有标记,直到一个线程被释放,系统将设置事件的状态为无标记;
如果没有线程在等待,则此事件将保持有标记,直到一个线程被释放。
BOOL ResetEvent(HANDLE hEvent);
这个函数把指定的事件对象设置为无信号状态。
WaitForSingleObject
等待事件
具体细节,现用现查吧,这些基本就够用了。
windows平台
创建 CreateEvent
销毁 CloseHandle
触发 SetEvent
广播 SetEvent / ResetEvent
等待 WaitForSingleObject
linux平台
创建 pthread_cond_init
销毁 pthread_cond_destroy
触发 pthread_cond_signal
广播 pthread_cond_broadcast
等待 pthread_cond_wait / pthread_cond_timedwait
demo:
#include "stdafx.h"
#include<Windows.h>
#include<iostream>
using namespace std;
HANDLE hEvent; //使用手动重置为无信号状态,初始化时有信号状态
DWORD WINAPI MyThread2(LPVOID lpParamter)
{
while (1)
{
WaitForSingleObject(hEvent,INFINITE); //有信号时才能得到
cout << "MyThread1 Runing :"<<"线程2"<<" "<<""<< endl;
Sleep(900);
//ResetEvent(hEvent);//重置为无信号状态
}
};
DWORD WINAPI MyThread1(LPVOID lpParamter)
{
while (1)
{
```
cout << "MyThread2 Runing"<<"线程1" <<" "<<""<< endl;
Sleep(1000);
SetEvent(hEvent);
}
```
};
void main()
{
hEvent = CreateEvent(NULL,TRUE,FALSE, NULL);
HANDLE hThread1 = CreateThread(NULL, 0, MyThread1, NULL, 0, NULL);
CloseHandle(hThread1);
HANDLE hThread2 = CreateThread(NULL, 0, MyThread2, NULL, 0, NULL);
CloseHandle(hThread2);
while(1);
return;
}