首先了解一下线程互斥的概念,线程互斥说白了就是在进程中多个线程的相互制约,如线程A未执行完毕,其他线程就需要等待!
线程之间的制约关系分为间接相互制约和直接相互制约。
所谓间接相互制约:一个系统中的多个线程必然要共享某种系统资源如共享CPU,共享打印机。间接制约即源于资源共享,线程A在打印的时候其他线程就要等待,否则打印的数据将变得非常混乱。间接相互制约称为互斥,互斥是同步的一种特殊形式
直接相互制约:主要指的是线程之间的一种递进关系,例如线程B运行的条件之一是需要线程A提供的参数,那么在线程A将数据传到线程B之前,线程B都将处于阻塞状态,称为同步。以后再说
(1)临界区,有的称为关键段,是定义在数据段中的一个CRITICAL_SECTION结构,确保在同一时间只有一个线程访问该数据段中的数据。计算机中大多数物理设备,进程中的共享变量等都是临界资源,它们要求被互斥访问,每个进程中访问的临界资源的代码称为临界区
写代码的时候可通过
CRITICAL_SECTION g_csThreadCode;
对临界区进行定义,但是在使用临界区之前首先要对临界区对象进行初始化,其函数原型如下:
InitializeCriticalSection(
_Out_ LPCRITICAL_SECTION lpCriticalSection
);
对临界区对象初始化完成后,线程访问临界区数据必须首先调用EnterCriticalSection函数申请进入临界区。在同一时间内,Windows只允许一个线程进入临界区。所以在申请的时候,如果有另一个线程在临界区的话,EnterCriticalSection函数将会一直等待下去,知道其他线程离开临界区才返回。EnterCriticalSection函数定义如下:
EnterCriticalSection(
_Inout_ LPCRITICAL_SECTION lpCriticalSection
);
当临界区对象操作完成后使用函数LeaveCriticalSection函数离开临界区,将临界区交还给Windows方便其他线程继续申请使用
LeaveCriticalSection(
_Inout_ LPCRITICAL_SECTION lpCriticalSection
);
用完临界区对象,使用DeleteCriticalSection函数将对象删除
DeleteCriticalSection(
_Inout_ LPCRITICAL_SECTION lpCriticalSection
);
例:用线程同时访问全局变量并对全局变量进行操作,如果不使用临界区访问,代码如下
#include
#include
#include
int g_nCount1 = 0;
int g_nCount2 = 0;
BOOL g_bContinue = TRUE;
UINT _stdcall ThreadFun(LPVOID);
int main(int argc, char *argv[])
{
HANDLE threads[2];
threads[0] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);
threads[1] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);
//等待1秒,结束两个计数线程,关闭句柄
Sleep(1000);
g_bContinue = FALSE;
WaitForMultipleObjects(2, threads, TRUE, INFINITE);
CloseHandle(threads[0]);
CloseHandle(threads[1]);
printf("g_nCount1 = %d\n", g_nCount1);
printf("g_nCount2 = %d\n", g_nCount2);
return 0;
}
UINT _stdcall ThreadFun(LPVOID)
{
while (g_bContinue)
{
g_nCount1++;
g_nCount2++;
}
return 0;
}
运行结果截图如下:
从运行结果可知,理论上来讲线程访问两个全局变量,其输出结果应该相同,者是因为同时访问g_nCount1和g_nCount2的两个线程具有相同的优先级,在执行过程中如果第一个线程取走g_nCount1的值准备进行自加操作的时候,他的时间敲好用完,系统切换到第二个线程去对g_nCount1进行自加操作,在一个时间片后第一个线程再次被调用,此事它会去除上次的值自加而非第二个线程自加后的值,这样值就会覆盖第二个线程操作得到的值。同样g_nCount2也存在相同的问题。
在添加临界区对象后,这种情况就不复存在了。如下
#include
#include
#include
int g_nCount1 = 0;
int g_nCount2 = 0;
BOOL g_bContinue = TRUE;
CRITICAL_SECTION g_csThread; //声明临界区对象
UINT _stdcall ThreadFun(LPVOID);
int main(int argc, char *argv[])
{
InitializeCriticalSection(&g_csThread); //初始化临界区对象
HANDLE threads[2];
threads[0] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);
threads[1] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);
//等待1秒,结束两个计数线程,关闭句柄
Sleep(1000);
g_bContinue = FALSE;
WaitForMultipleObjects(2, threads, TRUE, INFINITE);
CloseHandle(threads[0]);
CloseHandle(threads[1]);
DeleteCriticalSection(&g_csThread); //删除临界区对象
printf("g_nCount1 = %d\n", g_nCount1);
printf("g_nCount2 = %d\n", g_nCount2);
return 0;
}
UINT _stdcall ThreadFun(LPVOID)
{
while (g_bContinue)
{
EnterCriticalSection(&g_csThread); //申请进入临界区
g_nCount1++;
g_nCount2++;
LeaveCriticalSection(&g_csThread); //离开临界区
}
return 0;
}
运行结果如下:
感觉这个例子不是太好呢,下个函数换个典型的例子!!!!
总结:临界区的存在保证了多线程在同一时间只能有一个访问共享资源,保证了数据的一致性!
(2)互斥量mutex
互斥量是一个内核对象,用来确保一个线程独占一个资源的访问。互斥量与关键段行为非常相似,而且互斥量可以用于不同进程中的线程互斥访问资源。在C++11中与mutex相关的类(包括锁类型)和函数都声明在
首先创建互斥量:CreateMutex,查阅库函数发现#define CreateMutex CreateMutexW,追根溯源,直接看定义
CreateMutexW(
_In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes, //安全控制,一般直接传入NULL表示默认值
_In_ BOOL bInitialOwner, //参数用来确定互斥量的初始拥有者
_In_opt_ LPCWSTR lpName //设置互斥量的名称,NULL则为匿名互斥量
);
有必要对函数第二个参数单独进行说明bInitialOwner,互斥量的初始拥有者,如果传入TRUE表示互斥量对象内部会记录创建它的线程的线程ID号并将递归计数设置为1,由于该线程ID非零,所以互斥量处于未触发状态.如果传入FALSE,那么互斥量对象内部线程ID号将设置为NULL,递归计数设置为0,这意味着互斥量不为任何线程占用,处于触发状态。
打开互斥量OpenMutex,查看库函数,是OpenMutexW的重定义:#define OpenMutex OpenMutexW
HANDLE
WINAPI
CreateMutexW(
_In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes, //安全控制,一般直接传入NULL表示默认值
_In_ BOOL bInitialOwner, //参数用来确定互斥量的初始拥有者
_In_opt_ LPCWSTR lpName //设置互斥量的名称,NULL则为匿名互斥量
);
第三个参数lpName表示一个进程中的线程创建互斥量后,其它进程中的线程就可以通过这个函数来找到这个互斥量。
OpenMutex如果访问成功则返回一个表示互斥量的句柄,如果失败则返回NULL
触发互斥量:ReleaseMutex函数,其定义为:
BOOL
WINAPI
ReleaseMutex(
_In_ HANDLE hMutex
);
访问互斥资源前应该要调用等待函数WaitFor***(代码中有体现,或Single或Multi),结束访问时就要使用ReleaseMutex()来表示自己已经结束访问,其他线程可以开始访问.
示例代码:
#include
#include
#include
long g_nNum;
UINT _stdcall threadFun(LPVOID);
const int threadNum = 10;
HANDLE g_hThreadParameter;
CRITICAL_SECTION g_csThreadCode;
//HANDLE g_hThreadEvent; //声明内核事件
int main(int argc, char *argv[])
{
g_hThreadParameter = CreateMutex(NULL, FALSE, NULL); //生成mutex互斥量
InitializeCriticalSection(&g_csThreadCode); //初始化临界区
//g_hThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL); //定义内核事件
HANDLE handle[threadNum];
g_nNum = 0;
int i = 0;
while (i < threadNum)
{
handle[i] = (HANDLE)_beginthreadex(NULL, 0, threadFun, &i, 0, NULL);
WaitForSingleObject(g_hThreadParameter, INFINITE);
//WaitForSingleObject(g_hThreadEvent, INFINITE);
i++;
}
WaitForMultipleObjects(threadNum, handle, TRUE, INFINITE);
CloseHandle(g_hThreadParameter);
DeleteCriticalSection(&g_csThreadCode);
CloseHandle(handle);
for (i = 0; i < threadNum; i++)
{
CloseHandle(handle[i]);
}
//CloseHandle(g_hThreadEvent);
return 0;
}
UINT _stdcall threadFun(LPVOID pPM)
{
int nThreadNum = *(int *)pPM;
//SetEvent(g_hThreadEvent);
ReleaseMutex(g_hThreadParameter);
Sleep(100);
EnterCriticalSection(&g_csThreadCode);
g_nNum++;
Sleep(0);
printf("线程编号为%d 全局变量为%d\n", nThreadNum, g_nNum);
LeaveCriticalSection(&g_csThreadCode);
return 0;
}
运行结果如下:
现在还未涉及到内核事件,如果添加上内核事件代码(及代码中注释掉部分),其线程编号则会保证唯一性。如图所示:
#include
#include
#include
long g_nNum; //全局资源
unsigned int __stdcall Fun(void *pPM); //线程函数
const int THREAD_NUM = 10; //子线程个数
int main()
{
g_nNum = 0;
HANDLE handle[THREAD_NUM];
int i = 0;
while (i < THREAD_NUM)
{
handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);
i++;//等子线程接收到参数时主线程可能改变了这个i的值
}
//保证子线程已全部运行结束
WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
return 0;
}
unsigned int __stdcall Fun(void *pPM)
{
//由于创建线程是要一定的开销的,所以新线程并不能第一时间执行到这来
int nThreadNum = *(int *)pPM; //子线程获取参数
Sleep(50);//some work should to do
g_nNum++; //处理全局资源
Sleep(0);//some work should to do
printf("线程编号为%d 全局资源值为%d\n", nThreadNum, g_nNum);
return 0;
}
#include
#include
#include
#include
using namespace std;
long g_nNum;
UINT _stdcall threadFun(LPVOID);
const int threadNum = 10; //生成的子线程个数
HANDLE g_hThreadParameter;
CRITICAL_SECTION g_csThreadCode, g_csThreadNum;
//声明内核事件
HANDLE g_hThreadEvent;
int main()
{
g_hThreadParameter = CreateMutex(NULL, FALSE, NULL); //生成mutex互斥量
//初始化临界区
InitializeCriticalSection(&g_csThreadCode);
InitializeCriticalSection(&g_csThreadNum);
//初始化内核事件
g_hThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
HANDLE handle[threadNum];
g_nNum = 0;
int i = 0;
while (i < threadNum)
{
handle[i] = (HANDLE)_beginthreadex(NULL, 0, threadFun, &i, 0, NULL);
WaitForSingleObject(g_hThreadParameter, INFINITE);
WaitForSingleObject(g_hThreadEvent,INFINITE);
i++;
}
WaitForMultipleObjects(threadNum, handle, TRUE, INFINITE);
CloseHandle(g_hThreadParameter);
DeleteCriticalSection(&g_csThreadCode);
DeleteCriticalSection(&g_csThreadNum);
//CloseHandle(handle);
for (i = 0; i < threadNum; i++)
{
CloseHandle(handle[i]);
}
CloseHandle(g_hThreadEvent);
return 0;
}
UINT _stdcall threadFun(LPVOID pM)
{
EnterCriticalSection(&g_csThreadNum);
int nThreadNum = *(int*)pM;
SetEvent(g_hThreadEvent);
ReleaseMutex(g_hThreadParameter);
Sleep(100);
EnterCriticalSection(&g_csThreadCode);
g_nNum++;
Sleep(100);
cout << "线程ID: " << GetCurrentThreadId() << ", 编号为: " << nThreadNum << "数值为: " << g_nNum << endl;
LeaveCriticalSection(&g_csThreadCode);
LeaveCriticalSection(&g_csThreadNum);
return 0;
}