线程安全问题
✅产生线程安全问题两个必要条件,必须同时满足:
只有读、没有写,不会出现线程安全问题
DWORD g_dwTickets = 10;
//多个线程同时执行这段代码
while(g_dwTickets>0)
{
printf("还有:%d张票\\n",g_dwTickets);
g_dwTickets—;
printf("卖出一张,还有:%d张\\n",g_dwTickets);
}
故障现象:重复卖票、票数变成负数
根本原因:检查和修改不是原子操作 线程可以在任意 CPU 指令间隙发生线程切换
示例流程:
竞态条件:程序结果依赖线程调度切换时机,结果不可预测
临界资源、临界区
目标:保证同一时间,最多只有一个线程执行临界区代码
自己手写 if 判断令牌不行:判断 + 赋值两条 C 语句,会被线程切换打断,依旧有竞态
Windows 同步 API 底层依靠 CPU 硬件原子指令保证操作不可打断
CRITICAL_SECTION 临界区对象(用户态锁)
API 函数:
/
/初始化临界区对象
void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
//进入临界区:获取锁(拿令牌),拿不到就阻塞等待
void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
//离开临界区:释放锁(归还令牌)
void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
使用完整步骤
❗不能是局部栈变量,多个线程要访问同一个锁对象
错误写法:
//错误!判断在临界区外面
while(g_dwTickets>0)
{
EnterCriticalSection(&cs);
g_dwTickets—;
LeaveCriticalSection(&cs);
}
while 判断读取共享变量没有上锁,切换依然会出现竞态
正确写法:
while(1)
{
EnterCriticalSection(&cs);
if(g_dwTickets > 0)
{
g_dwTickets—;
printf("卖出一张,剩余:%d\\n",g_dwTickets);
LeaveCriticalSection(&cs);
}
else
{
LeaveCriticalSection(&cs);
break;
}
}
临界区使用权衡
CRITICAL_SECTION 关键特性
不能用CloseHandle;不需要句柄;销毁使用DeleteCriticalSection()
总结
#include <windows.h>
#include <stdio.h>
CRITICAL_SECTION g_cs;
DWORD g_ticket = 10;
DWORD WINAPI SellTicket(LPVOID p)
{
while(TRUE)
{
EnterCriticalSection(&g_cs);
if(g_ticket > 0)
{
g_ticket—;
printf("线程%X卖出一张,剩余=%d\\n",GetCurrentThreadId(),g_ticket);
LeaveCriticalSection(&g_cs);
}
else
{
LeaveCriticalSection(&g_cs);
break;
}
}
return 0;
}
int main()
{
InitializeCriticalSection(&g_cs);
HANDLE h1 = CreateThread(NULL,0,SellTicket,NULL,0,NULL);
HANDLE h2 = CreateThread(NULL,0,SellTicket,NULL,0,NULL);
WaitForSingleObject(h1,INFINITE);
WaitForSingleObject(h2,INFINITE);
CloseHandle(h1);
CloseHandle(h2);
DeleteCriticalSection(&g_cs);
return 0;
}





