【问题标题】:c++, member thread object causes crash on structure deletion when it's runningc++,成员线程对象在运行时导致结构删除崩溃
【发布时间】:2021-01-31 07:59:59
【问题描述】:

我有一个具有踢球功能的玩家类,我需要稍等片刻,然后由于我无法控制的原因踢出玩家,而且我还需要在等待时不持有该功能,所以我的问题是; 当线程完成等待并踢玩家时,断开连接事件被触发并且玩家的内存被立即释放,这导致明显未完成的线程在那一刻持续导致崩溃。

class Player
{
    public:
        Player();
        void kick();
        void kick(const char*, int color);

    protected:

        std::thread delaydKick_;

    private:
};

void Player::kick(){
    sampgdk_Kick(playerid); //tells API to kick the player
}


void Player::kick(const char* m, int color){
    sendMessage(m, color); // sends message to the player using API
    delaydKick_ = std::thread([p = this](){
        std::this_thread::sleep_for(std::chrono::seconds(3));
        p->kick(); //calls the function from the player object defined above
    });
    sampgdk::logprintf("Kicked (%s); %s", playername, m); //sends log to the server using API
}

//gets called by API when player disconnects
//PlayerDisconnectEvent just contains a pointer to the player which is on the heap
bool VirtualServerManager::OnPlayerDisconnect(PlayerDisconnectEvent&& e){
        //if disconnect is not kick
        if(e.player != nullptr){
            if( ( e.reason  < 2 ) && ( e.player->loggedIn == true ) ){
                e.player->getRefresh();
                e.player->updatePlayTime();
                SQLHandler sqlh(&sqlc);
                PlayerPersistance::save(e.player, &sqlh);
            }
            players.del(e.player->playerid); //here the player gets deleted when the api rises the event
        }
        return true;
    }

函数del的定义:

    template <typename T>
    void List<T>::del(size_t index){
        if(index >= current_max_){
            throw "pointer out of range";
        }
        if(items_[index] != nullptr){
            delete items_[index]; // it just calls delete on the pointer which is on the heap
            items_[index] = nullptr;
            count_--;
        }

}

需要澄清的是,包含线程对象的播放器堆上的内存被删除,因此终止线程对象 (delaydKick_) 会导致以下崩溃:

在没有活动异常的情况下终止调用

尝试在玩家的析构函数中加入线程会导致:

在抛出 'std::system_error' 的实例后调用终止 what():避免资源死锁

在创建线程后分离线程可以解决问题,但我想知道为什么会发生这些错误,尤其是当我在 Player 类析构函数上加入线程时。 另外我想知道是否有一种方法可以在不将线程与播放器分离的情况下解决这个问题。

【问题讨论】:

  • 您的问题中有很多内容,但并非所有内容都像需要的那样清楚。什么断开连接事件?什么内存被释放?崩溃发生在哪里?加入什么析构函数?这一切都不是显而易见的。请尝试提出minimal reproducible example
  • 这样更好吗?
  • 不,您的代码仍然依赖于未定义的“VirtualServerManager”之类的东西。尝试组合一个新的minimal reproducible example,它实际上可以按原样编译并演示您所询问的问题。
  • 已经得到了两个非常好的答案,无论如何感谢您的宝贵时间:3。

标签: c++ multithreading


【解决方案1】:

试图加入析构函数上的线程只会导致事情吐出我不明白的资源死锁异常,因为它不是试图加入自己而是试图加入成员对象

delaydKick_ = std::thread([p = this](){
    std::this_thread::sleep_for(std::chrono::seconds(3));
    p->kick(); //calls the function from the player object defined above
});

这里p-&gt;kick()仍在线程中时执行。因此,如果您在p-&gt;kick() 中删除您的播放器,并在析构函数中调用join(),您就是在尝试让线程自己加入。

您的应用程序中可能有一个事件循环,它处理诸如套接字消息之类的事件。

你可以做的是:

   delaydKick_ = std::thread([p = this](){
        std::this_thread::sleep_for(std::chrono::seconds(3));
        some_thread_safe_queue.push(this)
    });

// ...

// In your application's event loop:

while (1) {
  // ...

  while (some_thread_safe_queue.size > 0) {
    auto to_kick = some_thread_safe_queue.pop();
    to_kick->kick();
  }
}

您还可以查看co_awaitco_async,它们可以很好地解决您的问题。

【讨论】:

  • 这给了我一个想法,我认为 Imma 最终会使用线程池。顺便说一句,事件由 API 引发,它们是完全同步的。
  • 好吧。我打算编辑我​​的答案,说如果你有一个事件循环,你根本不需要线程 - 只需将需要被踢的时间的玩家添加到在事件循环中处理的某个结构中。无论如何祝你好运:)
  • 我没有事件循环,这是一个更好的主意,我没有落入那个解决方案,因为 API 需要同步事件处理,但我可以创建自己的事件,因为播放器我使用的类与 API 交互,而不是相反。非常感谢
【解决方案2】:

如果您想将玩家标识符传递给另一个线程,您需要使用某种标识符,该标识符保证在使用该标识符期间保持有效。一个简单的解决方案是使用共享指针,并让最后一个共享指针的销毁隐式释放播放器。

另一种解决方案是使用某种“玩家 ID”。当新线程需要访问播放器时,它会获取一个互斥锁,并检查全局播放器平板电脑的 ID 是否仍然有效。

但是,如果您要编写一个处理多个用户的服务器,并且这些用户需要相互交互,那么您需要选择一些方法来允许不同的上下文相互传递玩家。

    delaydKick_ = std::thread([p = this](){

该线程采用原始指针。不要在现代 C++ 中使用原始指针。但是,如果出于某种原因绝对必须这样做,则必须在某处跟踪引用计数器或类似的东西。你必须通过某种同步来保护它。

另外,如果你的kick 函数被调用,同时对同一连接的踢球已经在进行中,将会发生非常糟糕的事情。你应该检查一下。

我强烈建议不要将销毁与任何服务器级状态更改结合使用。你的玩家对象在被踢时应该进入“被踢”状态,在断开连接时应该进入“断开”状态。当且仅当所有线程都完成了对对象的操作并且它已从任何可能允许其他线程找到它的全局跟踪结构中删除时,才应该进行销毁。使用std::shared_ptr 为您完成所有这些工作。

【讨论】:

  • 非常感谢您的建议,我将牢记这一点,考虑到您刚才所说的内容,可能会更改一些代码,(这是我在 c++ 上创建的第一个真正的应用程序)。
  • @RafaelLópez 考虑使用weak_ptr 复制shared_ptr 的延迟踢(然后在处理它时检查它的有效性),因为你不想等待如果玩家在此期间被踢,则整整三秒
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多