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

如何修复多线程抢椅子游戏中的"terminate called without an active exception"错误

多线程抢椅子游戏"terminate called without an active exception"错误分析与修复

错误原因

  • 代码缩进逻辑错误,导致互斥锁未正确释放:musical_chairs函数中else分支仅包裹了not_capture(idx);,后续的玩家移除循环和闭合大括号属于游离代码,这会导致部分线程执行路径中mtx.unlock();无法被调用,互斥锁长期被持有,同时线程退出时未完成解锁操作,触发终止异常。
  • 线程对象未正确管理:当线程执行到错误分支提前退出时,线程对象在销毁前未被join,C++标准规定可join状态的线程对象被销毁时会调用std::terminate,这就是错误的直接根源。
  • 玩家列表遍历的越界隐患:原来的循环用remaining_player_num作为终止条件,但players在erase后大小会减小,容易导致越界访问。

修复方案

1. 修正代码块结构,保证锁的正确释放

把else分支的所有逻辑用大括号包裹,确保mtx.unlock();在所有执行路径下都能触发:

void musical_chairs(IntQueueHW6 &my_queue, unsigned int idx,unsigned int remaining_player_num, vector<int> &players)
{
    this_thread::sleep_until(chrono::system_clock::now() + chrono::seconds(2));
    mtx.lock();
    if(!(my_queue.isFull()))
    {
        my_queue.enqueue(idx);
        cout << "Player " << idx << " captured a chair at ";
        current_time();
        cout << "." << endl;
    }
    else
    {
        not_capture(idx);
        for(auto it = players.begin(); it != players.end(); ++it)
        {
            if(*it == idx)
            {
                players.erase(it);
                break;
            }
        }
    }
    mtx.unlock();
}

2. 使用RAII锁替代手动锁操作(更安全)

用std::lock_guard自动管理锁的生命周期,彻底避免手动解锁遗漏的问题:

#include <mutex>

std::mutex mtx; // 确保全局互斥锁正确声明

void musical_chairs(IntQueueHW6 &my_queue, unsigned int idx,unsigned int remaining_player_num, vector<int> &players)
{
    this_thread::sleep_until(chrono::system_clock::now() + chrono::seconds(2));
    std::lock_guard<std::mutex> lock(mtx); // 构造时自动加锁,销毁时自动解锁
    if(!(my_queue.isFull()))
    {
        my_queue.enqueue(idx);
        cout << "Player " << idx << " captured a chair at ";
        current_time();
        cout << "." << endl;
    }
    else
    {
        not_capture(idx);
        for(auto it = players.begin(); it != players.end(); ++it)
        {
            if(*it == idx)
            {
                players.erase(it);
                break;
            }
        }
    }
}

3. 修正线程容器的创建与join逻辑

main函数中,线程容器不需要按初始玩家数创建,按当前剩余玩家数创建即可,避免无效索引,同时简化join逻辑:

while(remaining_player_num != 1)
{
    IntQueueHW6 my_queue(total_round_num);
    // 按当前剩余玩家数创建线程容器
    vector<thread> threads;
    threads.reserve(players.size());

    cout << "Time is now ";
    current_time();
    cout << endl;

    for(int idx : players)
    {
        threads.emplace_back(&musical_chairs, ref(my_queue), idx, remaining_player_num, ref(players));
    }
    
    // 遍历所有线程完成join,确保没有线程处于可join状态被销毁
    for(auto& t : threads)
    {
        if(t.joinable())
        {
            t.join();
        }
    }
     
    display_remaining_players(players);
    remaining_player_num --;
    total_round_num -- ;
    my_queue.clear();
}

核心问题总结

你遇到的terminate called without an active exception主要是因为线程对象在可join状态下被销毁,而根源是代码缩进错误导致锁未正确释放、线程执行路径异常。通过修正代码块结构、使用RAII锁、正确管理线程生命周期,就能彻底解决这个问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 02:23:11