ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Win32临界区

Win32临界区 线程安全问题变量存储特点局部变量存在线程自己的栈空间每个线程独立副本多线程互不干扰天然线程安全全局变量 / 堆变量存全局地址空间所有线程共享同一份数据会产生线程安全问题✅产生线程安全问题两个必要条件必须同时满足多个线程访问同一个共享资源访问中包含写操作修改只有读、没有写不会出现线程安全问题售票案例竞态条件DWORD g_dwTickets10;//多个线程同时执行这段代码while(g_dwTickets0){printf(还有%d张票\n,g_dwTickets);g_dwTickets--;printf(卖出一张还有%d张\n,g_dwTickets);}故障现象重复卖票、票数变成负数根本原因检查和修改不是原子操作 线程可以在任意 CPU 指令间隙发生线程切换示例流程线程 A 判断 g_dwTickets0此时票数 1时间片到线程切换线程 B 抢到 CPU完整执行判断、自减票数变为 0切回线程 AA 继续执行g_dwTickets–票数直接变成 ‑1竞态条件程序结果依赖线程调度切换时机结果不可预测临界资源、临界区临界资源同一时刻只允许一个线程访问的共享资源全局变量、共享内存都属于临界资源临界区操作临界资源的那一段代码目标保证同一时间最多只有一个线程执行临界区代码自己手写 if 判断令牌不行判断 赋值两条 C 语句会被线程切换打断依旧有竞态Windows 同步 API 底层依靠 CPU 硬件原子指令保证操作不可打断CRITICAL_SECTION 临界区对象用户态锁API 函数//初始化临界区对象voidInitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);//进入临界区获取锁拿令牌拿不到就阻塞等待voidEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);//离开临界区释放锁归还令牌voidLeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);使用完整步骤定义全局 / 堆上 CRITICAL_SECTION cs;❗不能是局部栈变量多个线程要访问同一个锁对象主线程调用 InitializeCriticalSection(cs); 初始化所有读写共享变量的代码包括 if/while 判断全部包裹在 Enter ~ Leave 之间操作完成调用LeaveCriticalSection(cs);释放锁错误写法//错误判断在临界区外面while(g_dwTickets0){EnterCriticalSection(cs);g_dwTickets--;LeaveCriticalSection(cs);}while 判断读取共享变量没有上锁切换依然会出现竞态正确写法while(1){EnterCriticalSection(cs);if(g_dwTickets0){g_dwTickets--;printf(卖出一张剩余%d\n,g_dwTickets);LeaveCriticalSection(cs);}else{LeaveCriticalSection(cs);break;}}临界区使用权衡锁范围太小部分共享变量操作没上锁线程安全失效锁范围太大临界区内代码执行久其他线程大量等待并发性能下降CRITICAL_SECTION 关键特性用户态同步对象不是内核对象不能用CloseHandle不需要句柄销毁使用DeleteCriticalSection()同一个线程可以多次 EnterCriticalSection递归锁内部维护引用计数Enter 几次就必须 Leave 几次只用于同一个进程内部多线程同步不能跨进程使用获取不到锁的线程会阻塞总结局部变量在栈线程私有安全全局 / 堆共享多线程写会出问题线程安全两个条件多线程访问同一资源 存在写操作。只读不会出问题竞态条件检查和修改不是原子线程在中间切换逻辑错乱CRITICAL_SECTION临界区用户态锁仅本进程内使用不能跨进程所有对共享变量的判断、读写全部要包裹在 Enter‑Leave 中间不能把判断放锁外面Enter 几次就要 Leave 几次不能遗漏 Leave否则其他线程永久卡死等待#includewindows.h#includestdio.hCRITICAL_SECTION g_cs;DWORD g_ticket10;DWORD WINAPISellTicket(LPVOID p){while(TRUE){EnterCriticalSection(g_cs);if(g_ticket0){g_ticket--;printf(线程%X卖出一张剩余%d\n,GetCurrentThreadId(),g_ticket);LeaveCriticalSection(g_cs);}else{LeaveCriticalSection(g_cs);break;}}return0;}intmain(){InitializeCriticalSection(g_cs);HANDLE h1CreateThread(NULL,0,SellTicket,NULL,0,NULL);HANDLE h2CreateThread(NULL,0,SellTicket,NULL,0,NULL);WaitForSingleObject(h1,INFINITE);WaitForSingleObject(h2,INFINITE);CloseHandle(h1);CloseHandle(h2);DeleteCriticalSection(g_cs);return0;}
返回列表