记一次协程中使用线程锁造成的问题以及各种锁的总结

我习惯把进程或线程同步互斥的控制方法称之为”锁”,自旋锁、互斥锁、信号量、临界区各种各样的锁一大堆,这些名字看起来都特别高大上,但是其实他们就是实现了线程间对共享数据的一个安全访问,有些锁是可以用在不同进程的线程间的,比如互斥锁和信号量。有些锁可以设置进入这个锁的次数,比如信号量。如果你的程序不需要在多个进程的线程之间加锁,那么使用临界区就可以了。


我们的项目使用的大部分是临界区,在codis和协程上线后出现了一个问题,由于每个玩家是一个单独的协程,在多线程处理一块共享数据的时候,不同的协程进入同一个函数进行加锁操作,因为函数的后续进行了redis的异步访问,这些协程可能在没有解锁的情况下被挂起,如果这个函数的访问次数增多,那么其他需要处理共享数据的线程就会等待,造成某些逻辑的卡死。由于第一时间就想到了该问题的解决办法,所以并没有继续深入研究导致程序假死的具体步骤。

记录一下出问题的函数以及解决后的函数:

///获得角色的摘要信息指针
TRoleData * CRoleDataService::FindRoleData(const std::string & _roleid)
{
    SVR_BASE::CSVRCriticalEnter t_autolock(&m_lock);
    RoleMap::const_iterator t_it = m_mapRoleByRoleId.find(_roleid);
    if(t_it == m_mapRoleByRoleId.end())
    {
        SVR_BASE::coroutine_t t_coroutine = SVR_BASE::co_current();
        MASSERT( t_coroutine!=NULL );
        TRoleData *    t_ptrRole = new TRoleData();
        t_ptrRole->strRoleId = "";
        if ( true == CRoleDataService::Instance._redis_command_cb( REDIS_CLT::HGETALL(ROLE_SUMMARY::_SUMMARY_ROLE_+_roleid), 
            std_tr1::bind(_co_rolesummary_callback, t_coroutine, t_ptrRole, std_tr1::_1 )))
        {
            SVR_BASE::co_wait_io_complete();
        }
        if("" != t_ptrRole->strRoleId)
        {
            m_mapRoleByRoleId.insert(std::make_pair(_roleid,t_ptrRole));
        }
        else
        {
            delete t_ptrRole;
            t_ptrRole = NULL;
        }
        return t_ptrRole;
    }
    else
    {
        return t_it->second;
    }
}

上面的函数使用了我们项目中自动释放的临界区对象SVR_BASE::CSVRCriticalEnter来对m_mapRoleByRoleId数据进行加锁,这个对象的生命周期到函数退出为止,但是 SVR_BASE::co_wait_io_complete() 会把该协程挂起,如果这个协程不再被激活,那么锁将永远不会被解开,其他线程中用到这个锁的地方就会一直处于等待状态。修改后的代码:

///获得角色的摘要信息指针
TRoleData * CRoleDataService::FindRoleData(const std::string & _roleid)
{
    m_lock.lock();
    RoleMap::const_iterator t_it = m_mapRoleByRoleId.find(_roleid);
    if(t_it != m_mapRoleByRoleId.end())
    {
        m_lock.unlock();
        return t_it->second;
    }
    m_lock.unlock();
    SVR_BASE::coroutine_t t_coroutine = SVR_BASE::co_current();
    MASSERT( t_coroutine!=NULL );
    TRoleData *    t_ptrRole = new TRoleData();
    t_ptrRole->strRoleId = "";
    if ( true == CRoleDataService::Instance._redis_command_cb( REDIS_CLT::HGETALL(ROLE_SUMMARY::_SUMMARY_ROLE_+_roleid), 
        std_tr1::bind(_co_rolesummary_callback, t_coroutine, t_ptrRole, std_tr1::_1 )))
    {
        SVR_BASE::co_wait_io_complete();
    }
    if("" != t_ptrRole->strRoleId)
    {
        m_lock.lock();
        t_it = m_mapRoleByRoleId.find(_roleid);
        if ( t_it!= m_mapRoleByRoleId.end() )
        {
            delete t_it->second;
            m_mapRoleByRoleId.erase(t_it);
        }
        m_mapRoleByRoleId.insert(std::make_pair(_roleid,t_ptrRole));
        m_lock.unlock();
    }
    else
    {
        delete t_ptrRole;
        t_ptrRole = NULL;
    }
    return t_ptrRole;
}

修改后的代码只在操作m_mapRoleByRoleId这块共享数据的时候进行了加解锁操作,保证了锁不会被协程挂起。