You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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;
}

问题分析

  1. 上下文回滚的状态不一致:当checked == 1触发回滚时,工作线程被重置到第一次sem_wait(&sem_rs.edited)之后的状态,但此时调度线程已经完成了第一次sem_wait(&sem_rs.sus),且checked被重置为1,导致调度线程进入第二次循环。工作线程回滚后再次执行sem_post(&sem_rs.sus),但该操作对应第一次循环的信号量配对,与调度线程当前等待的第二次循环信号量不匹配,最终引发死锁。
  2. 全局变量无同步保护: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;
}

关键修改点

  1. 添加临界区保护:用CRITICAL_SECTION包裹全局变量的读写操作,消除竞态条件。
  2. 同步回滚状态:回滚线程上下文时,重置sem_rs.sus信号量的状态,确保信号量操作严格配对;同时重置checked到回滚点,保证调度逻辑与工作线程状态一致。
  3. 调整线程循环逻辑:工作线程改为循环执行直到checked >= 2,避免回滚后提前退出;调度线程退出前发送信号通知工作线程结束。

内容的提问来源于stack exchange,提问作者Jamin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 00:05:57