多线程并发访问冲突问题示例:
#include
#include
#include
//定义宏threadnumber用来设置创建线程的个数
#define threadnumber 50
//定义一个全局变量
int number;
DWORD WINAPI criticalFun(void *p);
//主方法
void main() {
//创建有threadnumber(50)个元素的HANDLE数组
HANDLE handle[threadnumber];
//使用循环创建threadnumber(50)个线程,家用一次最多只能创建64个线程,即threadnumber的值不能大于64
for (int i = 0; i < threadnumber; i++)
{
//创建一个线程返回一个句柄
handle[i] = CreateThread(NULL, 0, criticalFun, NULL, 0, NULL);
}
//等待所有线程执行完毕
WaitForMultipleObjects(threadnumber,handle,TRUE,INFINITE);
printf("\nnumber = %d\n",number);
system("pause");
}
DWORD WINAPI criticalFun(void *p) {
int i = 1;
while (i<1000)
{
//number递增
number++;
i++;
}
return 0;
}
上述程序开启了50个线程,每个线程都调用了number全局变量,并对number进行了递增操作,50个线程分别增加1000次则结果理论上应该为50000
程序执行结果:
第一次:
发现两次执行结果不同,且都不是50000
这是因为发生了多线程并发访问冲突问题,即多个线程访问同一个全局变量时会使全局变量的值无法预测
解决多线程并发访问冲突的方法有很多,包括使用临界区,线程事件等方式
WaitForMultipleObjects方法和WaitForSingleObject方法是Windows API函数
WaitForMultipleObjects方法作用于多个线程,WaitForSingleObject作用于单个线程
两函数用来检测线程事件的信号状态(通过一个HANDLE类型的值),在某一线程中调用该函数时,线程暂时挂起,如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回,线程继续执行,如果超时时间已经到达dwMilliseconds毫秒,但线程所等待的对象还没有变成有信号状态,线程也继续执行
简单的说,就是设置条件,让使用该方法的线程停止,当条件满足或达到预定时间时,线程继续执行
DWORD WINAPI WaitForSingleObject(
_In_ HANDLE hHandle,
_In_ DWORD dwMilliseconds
);
参数 | 含义 |
---|---|
hHandle | 目标线程的句柄 |
dwMilliseconds | 最长等待时间,超过此限制后线程将不再等待,而是继续执行 |
DWORD WINAPI WaitForMultipleObjects(
_In_ DWORD nCount,
_In_ const HANDLE *lpHandles,
_In_ BOOL bWaitAll,
_In_ DWORD dwMilliseconds
);
参数 | 含义 |
---|---|
nCount | lpHandles中指向的数组中的对象句柄数,对象句柄的最大数目为 MAXIMUM_WAIT_OBJECTS(64),此参数不能为零 |
*lpHandles | 一个句柄数组 |
bWaitAll | 是否等待所有线程 |
dwMilliseconds | 最长等待时间,超过此限制后线程将不再等待,而是继续执行 |
临界区指的是一个访问共用资源的程序片段无法同时被多个线程访问的特性。
临界资源是一次仅允许一个进程使用的共享资源。每次只准许一个进程进入临界区,进入后不允许其他进程进入。不论是硬件临界资源,还是软件临界资源,多个进程必须互斥地对它进行访问。
4个方法的参数均为一个CRITICAL_SECTION类型的临界区变量
临界区的初始化和销毁:
方法 | 功能 |
---|---|
InitializeCriticalSection | 初始化一个临界区 |
DeleteCriticalSection | 销毁一个临界区 |
进入和离开临界区,用于定义临界区的范围:
方法 | 功能 |
---|---|
EnterCriticalSection | 设置临界区的起始位置 |
LeaveCriticalSection | 设置临界区的结束位置 |
#include
#include
#include
#include
int num = 0;
#define threadnumber 10
//声明CRITICAL_SECTION 临界区变量
CRITICAL_SECTION critical;
DWORD WINAPI criticalFun(void *p);
int main() {
//初始化临界区
InitializeCriticalSection(&critical);
HANDLE handle[threadnumber];
for (int i = 0; i < threadnumber; i++)
{
handle[i] = CreateThread(NULL, 0, criticalFun, NULL, 0, NULL);
}
WaitForMultipleObjects(threadnumber, handle, TRUE, INFINITE);
//销毁临界区
DeleteCriticalSection(&critical);
printf("\nnumber = %d\n", num);
system("pause");
}
DWORD WINAPI criticalFun(void *p) {
int i = 1;
//进入临界区
EnterCriticalSection(&critical);
while (i<1000) {
num++;
i++;
}
//离开临界区
LeaveCriticalSection(&critical);
return 0;
}
互斥量是一个HANDLE 类型的值,它的使用方法和临界区类似,需要创建和删除。
互斥量就是多线程中锁的概念,对于一段定义了互斥量的程序,当某个线程的到这个互斥量时此线程才有执行权,当执行完毕后及时将互斥量释放,下一个线程获得互斥量并继续执行
方法 | 功能 |
---|---|
CreateMutex() | 创建一个互斥量 |
CloseHandle() | 销毁一个互斥量 |
WaitForSingleObject | 设置程序权限,只有获取了互斥量的线程才有执行权 |
ReleaseMutex | 释放互斥量 |
#include
#include
#include
#include
#define threadnumber 54
int num = 0;
//声明一个互斥量
HANDLE mutex;
DWORD WINAPI mutexFun(void *p);
int main() {
//CreateMutex创建一个互斥量,该方法的3个参数的含义及设置值依次:安全性设为NULL代表不设置安全性,是否立即激活设为FALSE不立即使用,id设为NULL不设置
mutex = CreateMutex(NULL,FALSE,NULL);
HANDLE handle[threadnumber];
for (int i = 0; i < threadnumber; i++)
{
handle[i] = CreateThread(NULL, 0, mutexFun, NULL, 0, NULL);
}
WaitForMultipleObjects(threadnumber, handle, TRUE, INFINITE);
//销毁互斥量
CloseHandle(mutex);
printf("\nnumber = %d\n", num);
system("pause");
}
DWORD WINAPI mutexFun(void *p) {
int i = 0;
//设置权限,只有拿到互斥量的线程才可以执行
WaitForSingleObject(mutex,INFINITE);
while (i<1000) {
num++;
i++;
}
//释放互斥量,其他线程获取后继续执行
ReleaseMutex(mutex);
return 0;
}