【问题标题】:std::vector::erase() (multithreaded) 'Assertion `px != 0' failed.'std::vector::erase() (多线程)'断言 `px != 0' 失败。'
【发布时间】:2013-07-21 14:30:46
【问题描述】:

类似于shared_ptr Assertion px != 0 failed

我正在编写一个游戏服务器,它会生成一个新线程来处理每个用户会话。主线程有一个 std::vector 的 UserSession 共享指针。另一个线程会定期从该向量中删除死会话,但在执行 std::vector::erase() 时失败。我不知道我的生活出了什么问题。

错误是:

Prototype2: /usr/include/boost/smart_ptr/shared_ptr.hpp:653: typename boost::detail::sp_member_access::type boost::shared_ptr::operator->() const [with T = UserSession; 类型名 boost::detail::sp_member_access::type = UserSession*]: 断言 'px != 0' 失败。 中止(核心转储)

相关代码为:

void GameServer::start()
{
    int sessionid;
    boost::asio::io_service io_service;
    tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), port_));
    boost::thread(&GameServer::session_monitor, this);

    for (;;)
    {   
        socket_shptr socket(new tcp::socket(io_service));
        acceptor.accept(*socket);
        sessionid = numsessions_++;
        UserSession* usession = new 
            UserSession(socket, sessionid, io_service);
        session_shptr session(usession);

        sessions_mutex_.lock();
        sessions_.push_back(session);
        sessions_mutex_.unlock();

        std::cout << "Starting session for client " <<
            get_client_ip(*socket) << std::endl;

        session->start();
    }   
}

void GameServer::session_monitor()
{
    for (;;)
    {   
        boost::this_thread::sleep(boost::posix_time::seconds(10));
        std::cout << "Removing dead user sessions" << std::endl;

        sessions_mutex_.lock();
        for (std::vector<session_shptr>::iterator it = sessions_.begin();
            it != sessions_.end(); ++it)
        {
            if ((*it)->is_dead())
            {
                std::cout << "Removing session: " << (*it)->id() <<
                    std::endl;

                sessions_.erase(it);
            }
        }
        sessions_mutex_.unlock();
    }
}

【问题讨论】:

  • 无关:始终使用make_shared。使用std::lock_guard 而不是直接锁定。
  • 我还意识到这段代码还有其他问题,例如并发使用 std::cout。

标签: c++ boost stl boost-asio shared-ptr


【解决方案1】:

在迭代器上调用 erase 会使它失效。然后,您尝试继续使用它来遍历列表。您需要使用erase 的返回值继续遍历列表。

    for (std::vector<session_shptr>::iterator it = sessions_.begin(); it != sessions_.end(); )
    {
        if ((*it)->is_dead())
        {
            std::cout << "Removing session: " << (*it)->id() <<
                std::endl;

            it = sessions_.erase(it);
        }
        else
          ++it;
    }

【讨论】:

    【解决方案2】:

    std::vector 中删除内容的正确方法是使用remove-erase 习惯用法。循环遍历容器并手动删除元素既烦人又不会提高效率,而且容易出错,因为 erase 会使您的迭代器无效。

    std::removestd::remove_if 已经是它的非常巧妙的实现,我们可以将它们与对 erase 的调用捆绑在一起,这样您编写的唯一代码就是一个擦除到另一个不同的代码。

    这里是这个成语的基于容器的版本:

    template<typename Container, typename Lambda>
    Container&& remove_erase_if( Container&& c, Lambda&& test ) {
      using std::begin; using std::end;
      auto it = std::remove_if( begin(c), end(c), std::forward<Lambda>(test) );
      c.erase(it, c.end());
      return std::forward<Container>(c);
    }
    template<typename Container, typename T>
    Container&& remove_erase( Container&& c, T&& test ) {
      using std::begin; using std::end;
      auto it = std::remove( begin(c), end(c), std::forward<T>(test) );
      c.erase(it, c.end());
      return std::forward<Container>(c);
    }
    

    现在您的代码如下所示:

    sessions_mutex_.lock();
    remove_erase_if( sessions_, []( session_shptr& ptr )->bool {
      if (ptr->is_dead()) {
        std::cout << "Removing session: " << ptr->id() << std::endl;
        ptr.reset(); // optional
        return true;
      } else {
        return false;
      }
    });
    sessions_mutex_.unlock();
    

    或者,更短:

    sessions_mutex_.lock();
    remove_erase_if( sessions_, []( session_shptr& ptr ) {
      return ptr->is_dead();
    });
    sessions_mutex_.unlock();
    

    作为最后一个问题,请注意,如果您的析构函数可以重入,您必须非常小心 std::vector 的状态——如果析构函数代码导致您正在处理的 vector 被更改,你有麻烦了。

    如果这是一个问题,您可以创建一个临时向量来填充死进程:

    std::vector<session_shptr> tmp;
    sessions_mutex_.lock();
    remove_erase_if( sessions_, []( session_shptr& ptr ) {
      if (!ptr->is_dead())
        return false;
      tmp.emplace_back( std::move(ptr) );
      return true;
    });
    sessions_mutex_.unlock();
    tmp.clear();
    

    它将会话的破坏移出锁定(好!),并将其移出迭代几乎全球可访问的vector的代码(太棒了!)。

    这确实使用了一些 C++11 结构,但如果您的编译器是纯 C++03 且不会造成太大损坏,则可以去除大部分结构。 (去掉forwards,在Container上用&amp;替换&amp;&amp;,在LambdaT上用&amp;&amp;替换const &amp;

    您必须将 lambda 编写为函数或函数对象,或者编写为 erase_remove_if 调用的 bind

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-02-28
      • 2014-01-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-24
      • 2012-03-07
      相关资源
      最近更新 更多