使用WinAPI与信号量时sem_post()随机阻塞的问题求助
解决WinAPI与信号量实现可重复运行逻辑中的死锁问题
以下是一段使用WinAPI与信号量实现可重复运行逻辑的C++代码,程序能在若干次循环中正常输出,但随后会出现死锁:工作线程卡在sem_post(&sem_rs.sus)处,调度线程卡在sem_wait(&sem_rs.sus)处。
#include <iostream> #include <stdlib.h> #include <processthreadsapi.h> #include <Windows.h> #include <semaphore.h> #include <unistd.h> #include <pthread.h> #include <stdio.h> #include <stdarg.h> #include <strsafe.h> void ErrorExit(LPTSTR lpszFunction) { // Retrieve the system error message for the last-error code LPVOID lpMsgBuf; LPVOID lpDisplayBuf; DWORD dw = GetLastError(); FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL ); // Display the error message and exit the process lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, (lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR)); StringCchPrintf((LPTSTR)lpDisplayBuf, LocalSize(lpDisplayBuf) / sizeof(TCHAR), TEXT("%s failed with error %d: %s"), lpszFunction, dw, lpMsgBuf); MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK); LocalFree(lpMsgBuf); LocalFree(lpDisplayBuf); ExitProcess(dw); } typedef struct { sem_t edited; sem_t sus; sem_t running; } sem_res_sus; sem_res_sus sem_rs; int checked = 0; int isSuspend = 0; int rollbackPoint = -1; // producer DWORD WINAPI thread(LPVOID arg) { // edited isSuspend to 1 then can running and then suspended by the process sem_wait(&sem_rs.edited); sem_post(&sem_rs.sus); while(isSuspend); sem_post(&sem_rs.running); std::cout << "thread resume from sus!" << std::endl; sem_wait(&sem_rs.edited); sem_post(&sem_rs.sus); while(isSuspend); sem_post(&sem_rs.running); std::cout << "thread resume from sus again!" << std::endl; return EXIT_SUCCESS; } DWORD WINAPI scheduler(LPVOID arg) { CONTEXT a[2]; a[0].ContextFlags = CONTEXT_ALL; a[1].ContextFlags = CONTEXT_ALL; HANDLE hThread = (HANDLE) arg; while(checked < 2) { sem_wait(&sem_rs.running); isSuspend = 1; // edited isSuspend to 1 tell thread can running and then suspended by the process sem_post(&sem_rs.edited); // The process wait thread to tell him it can be suspended sem_wait(&sem_rs.sus); if (SuspendThread(hThread) == -1) { ErrorExit(TEXT("SuspendThread")); } if (GetThreadContext(hThread, &a[checked]) == 0) { ErrorExit(TEXT("GetThreadContext")); } if (checked == 1) { // 假设此时发生错误 // 回退到上一个检查点后,需要记录该次回卷点。 rollbackPoint = 0; if (SetThreadContext(hThread, &a[0]) == 0) { ErrorExit(TEXT("SetThreadContext")); } } else { rollbackPoint = -1; } if(ResumeThread(hThread) == -1) { ErrorExit(TEXT("ResumeThread")); } else { isSuspend = 0; } checked++; if (rollbackPoint != -1) { // 若回退了 checked = rollbackPoint + 1; rollbackPoint = -1; } } } int main() { sem_init(&sem_rs.edited, 0, 0); sem_init(&sem_rs.sus, 0, 0); sem_init(&sem_rs.running, 0, 1); DWORD t; std::cout << "start execution" << std::endl; HANDLE hThread = CreateThread( NULL, 0, thread, NULL, 0, &t ); DWORD t_h; HANDLE control_hThread = CreateThread( NULL, 0, scheduler, hThread, 0, &t_h ); WaitForSingleObject(hThread, INFINITE); WaitForSingleObject(control_hThread, INFINITE); CloseHandle(hThread); sem_close(&sem_rs.sus); sem_close(&sem_rs.edited); return EXIT_SUCCESS; }
问题分析
- 上下文回滚的状态不一致:当
checked == 1触发回滚时,工作线程被重置到第一次sem_wait(&sem_rs.edited)之后的状态,但此时调度线程已经完成了第一次sem_wait(&sem_rs.sus),且checked被重置为1,导致调度线程进入第二次循环。工作线程回滚后再次执行sem_post(&sem_rs.sus),但该操作对应第一次循环的信号量配对,与调度线程当前等待的第二次循环信号量不匹配,最终引发死锁。 - 全局变量无同步保护:
checked、isSuspend等全局变量被多线程直接访问,存在竞态条件,加剧了死锁的随机性。
解决方案
修改后的代码
#include <iostream> #include <stdlib.h> #include <processthreadsapi.h> #include <Windows.h> #include <semaphore.h> #include <unistd.h> #include <pthread.h> #include <stdio.h> #include <stdarg.h> #include <strsafe.h> CRITICAL_SECTION cs; void ErrorExit(LPTSTR lpszFunction) { LPVOID lpMsgBuf; LPVOID lpDisplayBuf; DWORD dw = GetLastError(); FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL ); lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, (lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR)); StringCchPrintf((LPTSTR)lpDisplayBuf, LocalSize(lpDisplayBuf) / sizeof(TCHAR), TEXT("%s failed with error %d: %s"), lpszFunction, dw, lpMsgBuf); MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK); LocalFree(lpMsgBuf); LocalFree(lpDisplayBuf); ExitProcess(dw); } typedef struct { sem_t edited; sem_t sus; sem_t running; } sem_res_sus; sem_res_sus sem_rs; int checked = 0; int isSuspend = 0; int rollbackPoint = -1; DWORD WINAPI thread(LPVOID arg) { while (true) { sem_wait(&sem_rs.edited); EnterCriticalSection(&cs); bool needExit = (checked >= 2); LeaveCriticalSection(&cs); if (needExit) break; sem_post(&sem_rs.sus); while(isSuspend); sem_post(&sem_rs.running); EnterCriticalSection(&cs); int currentChecked = checked; LeaveCriticalSection(&cs); if (currentChecked == 1) { std::cout << "thread resume from sus!" << std::endl; } else if (currentChecked == 2) { std::cout << "thread resume from sus again!" << std::endl; break; } } return EXIT_SUCCESS; } DWORD WINAPI scheduler(LPVOID arg) { CONTEXT a[2]; a[0].ContextFlags = CONTEXT_ALL; a[1].ContextFlags = CONTEXT_ALL; HANDLE hThread = (HANDLE) arg; while(true) { EnterCriticalSection(&cs); if (checked >= 2) { LeaveCriticalSection(&cs); break; } LeaveCriticalSection(&cs); sem_wait(&sem_rs.running); EnterCriticalSection(&cs); isSuspend = 1; LeaveCriticalSection(&cs); sem_post(&sem_rs.edited); sem_wait(&sem_rs.sus); if (SuspendThread(hThread) == -1) { ErrorExit(TEXT("SuspendThread")); } if (GetThreadContext(hThread, &a[checked]) == 0) { ErrorExit(TEXT("GetThreadContext")); } bool needRollback = false; EnterCriticalSection(&cs); if (checked == 1) { // 假设此时发生错误 rollbackPoint = 0; needRollback = true; } else { rollbackPoint = -1; } LeaveCriticalSection(&cs); if (needRollback) { if (SetThreadContext(hThread, &a[0]) == 0) { ErrorExit(TEXT("SetThreadContext")); } // 重置信号量状态,确保回滚后操作配对 sem_wait(&sem_rs.sus); // 重置检查点 EnterCriticalSection(&cs); checked = rollbackPoint; rollbackPoint = -1; LeaveCriticalSection(&cs); } if(ResumeThread(hThread) == -1) { ErrorExit(TEXT("ResumeThread")); } else { EnterCriticalSection(&cs); isSuspend = 0; if (!needRollback) { checked++; } LeaveCriticalSection(&cs); } } // 通知工作线程退出循环 sem_post(&sem_rs.edited); return EXIT_SUCCESS; } int main() { InitializeCriticalSection(&cs); sem_init(&sem_rs.edited, 0, 0); sem_init(&sem_rs.sus, 0, 0); sem_init(&sem_rs.running, 0, 1); DWORD t; std::cout << "start execution" << std::endl; HANDLE hThread = CreateThread( NULL, 0, thread, NULL, 0, &t ); DWORD t_h; HANDLE control_hThread = CreateThread( NULL, 0, scheduler, hThread, 0, &t_h ); WaitForSingleObject(hThread, INFINITE); WaitForSingleObject(control_hThread, INFINITE); CloseHandle(hThread); CloseHandle(control_hThread); sem_close(&sem_rs.sus); sem_close(&sem_rs.edited); sem_close(&sem_rs.running); DeleteCriticalSection(&cs); return EXIT_SUCCESS; }
关键修改点
- 添加临界区保护:用
CRITICAL_SECTION包裹全局变量的读写操作,消除竞态条件。 - 同步回滚状态:回滚线程上下文时,重置
sem_rs.sus信号量的状态,确保信号量操作严格配对;同时重置checked到回滚点,保证调度逻辑与工作线程状态一致。 - 调整线程循环逻辑:工作线程改为循环执行直到
checked >= 2,避免回滚后提前退出;调度线程退出前发送信号通知工作线程结束。
内容的提问来源于stack exchange,提问作者Jamin
相关产品推荐
相关产品推荐

