【问题标题】:issue with std::map std::findstd::map std::find 的问题
【发布时间】:2012-12-06 17:52:50
【问题描述】:

我有工作的 C++ 守护进程。问题是守护程序每月崩溃一到两次。正如您从下面的 GDB 输出中看到的那样,当守护进程在 std::map <unsigned int const, SessionID*> 容器中搜索 unsigned int sessionID 时会发生这种情况。我无法重现该问题,并认为它可能与来自用户的数据有问题(可能std::sting cookie_ssid 有一些意外数据,并且在使用strtoul 转换后出现问题。(知道,这不是正确的方法获取unsigned int)

守护进程崩溃后我只有.core 文件。并在if (!_M_impl._M_key_compare(_S_key(__x), __k)) 看到这个问题。任何想法如何解决这个问题?非常感谢。

GDB 输出:

#0  std::_Rb_tree<unsigned int, std::pair<unsigned int const, SessionID*>, std::_Select1st<std::pair<unsigned int const, SessionID*> >, std::less<unsigned int>, std::allocator<std::pair<unsigned int const, SessionID*> > >::find (this=0x529df15c, __k=@0x7f5fab7c)
    at stl_tree.h:1376
##########
1376            if (!_M_impl._M_key_compare(_S_key(__x), __k))
##########
#0  std::_Rb_tree<unsigned int, std::pair<unsigned int const, SessionID*>, std::_Select1st<std::pair<unsigned int const, SessionID*> >, std::less<unsigned int>, std::allocator<std::pair<unsigned int const, SessionID*> > >::find (
    this=0x529df15c, __k=@0x7f5fab7c) at stl_tree.h:1376
#1  0x0805e6be in TR::find_session (this=0x529df110,
    cookie_ssid=@0x47ef3614, ptr_to_ptr_session=0x7f5fac7c)
    at stl_map.h:542

函数TR::find_session是这样发布的:

bool TR::find_session ( const std::string &cookie_ssid, SessionID **ptr_to_ptr_session )
{
    unsigned int uint_sessionid = std::strtoul ( cookie_ssid.c_str(),NULL,0);

MUTEX_map_sessionids.lock_reading();
    std::map<unsigned int, SessionID*>::iterator it_sessionids = map_sessionids.find( uint_sessionid );

    if ( it_sessionids != map_sessionids.end() )
    { // exists
        *ptr_to_ptr_session = it_sessionids->second;
        MUTEX_map_sessionids.unlock();
        return true;
    }

MUTEX_map_sessionids.unlock();  
return false;   
}

编辑 我的清理功能在分离的线程上工作(每分钟一次或 5 分钟)。根据 cmets 的要求。我不确定这个功能。也许它的越野车......

void TR::cleanup_sessions () // not protected from multithread using! used only at one thread
{
std::list<SessionID*> list_to_clean; // tmplary store sessions to delete

MUTEX_map_sessionids.lock_reading();
std::map<unsigned int, SessionID*>::iterator it_sessionids = map_sessionids.begin();
MUTEX_map_sessionids.unlock();

while ( true )
{
    MUTEX_map_sessionids.lock_writing();
    if (it_sessionids == map_sessionids.end() )
    {
        MUTEX_map_sessionids.unlock();
        break;
    }

    SessionID *ptr_sessionid = it_sessionids->second;

    time_t secondsnow = time (NULL);

    ptr_sessionid->MUTEX_all_session.lock_reading();
    time_t lastaccesstime = ptr_sessionid->last_access_time;
    size_t total_showed = ptr_sessionid->map_showed.size(); 
    ptr_sessionid->MUTEX_all_session.unlock();


    if ( lastaccesstime and secondsnow - lastaccesstime > LOCALSESSION_LIFETIME_SEC ) // lifetime end!
    {
        // delete session from map
        map_sessionids.erase( it_sessionids++ ); // Increments the iterator but returns the original value for use by erase
        MUTEX_map_sessionids.unlock();              


        list_to_clean.push_back ( ptr_sessionid ); // at the end
    }
    else if ( total_showed == 0 and secondsnow - lastaccesstime > 36000 ) // not active for N secontes
    {
        map_sessionids.erase( it_sessionids++ ); // Increments the iterator but returns the original value for use by erase
        MUTEX_map_sessionids.unlock();

        // add pointer to list to delete it latter
        list_to_clean.push_back ( ptr_sessionid ); // at the end            
    }
    else
    {
        ++it_sessionids; // next
        MUTEX_map_sessionids.unlock();              
    }

}

// used? pause
if ( !list_to_clean.empty() ) 
{
    //sleep(1);
}

// cleanup session deleted from working map
while ( !list_to_clean.empty() )
{
    SessionID *ptr_sessionid_to_delete = list_to_clean.front();
    list_to_clean.pop_front();

    ptr_sessionid_to_delete->MUTEX_all_session.lock_writing(); // protected lock session mutex. can not delete session if its already locked. (additational protection)
    ptr_sessionid_to_delete->cleanup();
    delete ptr_sessionid_to_delete;
}

}

注意您可以在每次迭代中看到我锁定/解锁 map_sessions,因为此时其他线程会查找/插入新会话及其关键,因为用户不能等待。

【问题讨论】:

  • 互斥体的存在意味着代码是多线程的,因此它很可能是一个同步错误(您是否在某处修改映射而不持有互斥体?)。此外,您确实应该使用 RAII 来锁定/解锁互斥锁,虽然在这里可能没问题,但手动锁定/解锁互斥锁的 C++ 代码不太可能是异常安全的。
  • @user786653 我认为不,这不是互斥锁问题。我非常小心地注意互斥锁。并且 gdb 报告它在 std::find 或 std::compare.... :(
  • 您是否尝试过在 helgrind 下运行您的守护进程,它可以帮助捕捉您可能错过的东西? (valgrind --tool=helgrind ./your-daemon)。还可以尝试检查 gdb 中的一些值(例如 _M_impl 是否指向正常的东西?会话 id 是否合理,它是否存在于地图中?
  • 如果您使用范围锁而不是手动锁定和解锁它们,您可以花更少的时间观察互斥锁。
  • @abrahab,请告诉我们您程序中修改map_sessionids的所有位置(在地图中插入/删除/更新数据)。

标签: c++ debugging gdb daemon


【解决方案1】:

请注意,对映射的任何修改都可能使该映射中的任何迭代器无效。你有:

MUTEX_map_sessionids.lock_reading();
std::map<unsigned int, SessionID*>::iterator it_sessionids = map_sessionids.begin();
MUTEX_map_sessionids.unlock();

现在,在此解锁之后,其他线程可能会立即获取锁并执行一些使it_sessionids 无效的操作,这将使其处于后续代码将破坏映射的状态,从而导致以后的崩溃。

您需要在迭代器的整个生命周期内获取并持有一个锁。看起来你有读/写锁,所以你只需要一直持有读锁,当你想修改地图时将其升级为写锁,然后在修改后立即将其降级为读锁。长时间持有读锁只会阻塞其他想获取写锁的线程,不会阻塞其他只需要读锁的线程。

在评论中回答你的问题:

  1. 如果您不能长时间持有锁,那么您就不能拥有长时间保持有效的迭代器。您可以做的一件事是偶尔记住您在地图中的大致位置,释放锁(给其他线程一个机会并使迭代器无效),然后重新获取锁并在大致相同的点创建一个新的迭代器。您可以在循环中间添加类似这样的内容:

    if (++count > limit) { // only do this every Nth iteration
        unsigned int now_at = it_sessionids->first;
        MUTEX_map_sessionids.unlock();
        // give others a chance
        MUTEX_map_sessionids.lock_reading();
        it_sessionids = map_sessionids.lower_bound(now_at);
        count = 0; }
    
  2. 将锁从只读升级为读/写是您的实现可能不支持的对读/写锁的基本操作。如果没有,那么你就很不走运,需要一直持有作家锁。

【讨论】:

  • 两个问题 1) 长时间不能锁怎么办?其他线程从这张地图写入/读取,并且那里的动作必须我更优先。 2)如何“升级”或“降级”锁(互斥锁)?如果我将 unlock_read() 而不是立即 lock_writing() - 其他线程可能会在这些操作之间获得锁互斥锁
  • 关于第 1 点:也许最好从 map 的 begin() 开始,然后每 1000 次迭代解锁互斥锁,记住迭代结束的 sessionID。之后 LOCK 重复互斥并继续 .find(SessionID) 以获取其他 1000 个未处理迭代(然后循环)的位置?似乎它给了其他线程一些时间来处理和回答客户......
  • @Chris Dodd Note that any modification of a map may invalidate any iterator in that map。你能对此发表评论吗?我已经阅读了stackoverflow.com/questions/6438086/iterator-invalidation-rules 并且似乎地图迭代器在any modification 上没有失效。
  • @skwlisp:是的,你可以做一些不会使某些迭代器失效的mofications。在实践中,MOST 修改不会使 MOST 迭代器失效——这就是为什么这个 bug 大约一个月才会出现一次。但是,如果不仔细检查程序中对地图的所有其他修改,您就无法确定自己不会遇到任何问题。
猜你喜欢
  • 1970-01-01
  • 2012-02-05
  • 1970-01-01
  • 2022-08-24
  • 1970-01-01
  • 2017-07-18
  • 1970-01-01
  • 1970-01-01
  • 2011-01-19
相关资源
最近更新 更多