2

以相对简单的形式考虑一个线程安全的 getter 方法:

std::map<std::string, boost::shared_ptr<AAA> > repo;
AAA & get(const std::string &key)
{
    boost::upgrade_lock<boost::shared_mutex> lock(repoMutex);
    std::map<std::string, boost::shared_ptr<AAA> >::iterator it = repo.find(key);
    if(it == repo.end()){       
        boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
        boost::shared_ptr<AAA> t(new AAA(key));
        repo.insert(std::make_pair(key,t));
        return *t;
    }
    return *it->second;
}

在上面,我使用共享(可升级)锁来保护查找操作,并且仅当我需要插入键/值时才升级到唯一锁。到目前为止,一切都很好?

我想象的是以下(如果我的概念在任何步骤中是错误的,请告诉我):

  1. 两个线程进入方法

  2. 两者都允许同时运行repo.find()同一个密钥(并且该密钥不存在)。

  3. 他们都失败了。因为密钥不存在。

  4. 第一个线程通过进入升级区域获得独占访问,而第二个线程等待进入升级区域。

  5. 第一个线程完成为 key 创建新条目的工作,然后离开。

  6. 第二个线程进入并覆盖第一个线程插入的键/值。(这不是任何人想要的)

我们如何解决这个问题?谢谢

4

1 回答 1

1

简而言之,您当前的解决方案几乎没有任何问题。

首先,第二个线程不会覆盖第一个线程写入的数据,因为map::insert()只插入新键。您只需要检查是否insert真的插入了元素并返回相应的值。

唯一的担忧是可能不需要的无用创建t。在这种情况下,您可以在锁定后添加另一个检查:

std::map<std::string, boost::shared_ptr<AAA> >::iterator it = repo.find(key);
if(it == repo.end()){       
    boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
    if (repo.find(key) == repo.end() {
        ...
    }
}

但是您应该分析您的代码,看看这是否会给您带来任何优势。

此外,您可以使用map::insert()with hint 来避免重复搜索密钥:

std::map<std::string, boost::shared_ptr<AAA> >::iterator it = repo.find(key);
if(it == repo.end()){       
    boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
    it = repo.lower_bound(key);
    if (it->first != key)
        boost::shared_ptr<AAA> t(new AAA(key));
        repo.insert(it, std::make_pair(key,t));
        return *t;        
    } else {
        return *it->second;
    }
}
于 2014-09-26T12:42:44.783 回答