同步、线程安全接口的正确锁定模型
Proper locking model for synchronous, thread safe interface
我正在设计一个小型图书馆作为玩具项目的一部分。简化后,它会是这样的:
class FoobarManager {
public:
// Creates a new Foobar object internally an returns its ID
int createNewFoobar();
// Makes the Foobar object identified by foobarId frobnicate
void frobnicate(int foobarId);
// Removes the internal Foobar object identified by foobarId
void removeFoobar(int foobarId);
private:
// ID - Foobar
std::map<int, Foobar> allFoobars;
};
我的想法是我可以同时拥有多个 Foobar
,每个都有一个 ID,我可以用它来请求 FoobarManager 来操作它们。我的目标是制作图书馆:
- 线程安全:不假设调用每个方法的线程。
- 同步:我希望
createNewFoobar()
到 return 一个 Foobar
,而不是提供一个 onFoobarCreated()
回调。
- 从不同
Foobar
对象的角度来看尽可能独立:不要在一个对象 frobnicating 时锁定所有 Foobar
对象。
我似乎找不到一个好的锁定模型来满足所有这些要求。我想我至少需要一个 mutex
每个 Foobar
,和另一个 mutex
来控制地图中的 insertion/deletion。
插入新对象与 frobnicate
一起工作似乎很容易:
int createNewFoobar() {
std::lock_guard<std::mutex> mapLock(mapMutex);
allFoobars[++nextId] = Foobar();
return nextId;
}
void frobnicate(int foobarId) {
// Not using lock_guard because we need to intertwine with another lock
mapMutex.lock();
if (allFoobars.count(foobarId) == 0) return;
Foobar& fb = allFoobars.at(foobarId);
// Lock on the Foobar mutex
// ...
mapMutex.unlock();
fb.frobnicate();
// Unlock the Foobar mutex
// ...
}
但是,我无法弄清楚如何在不使 frobnicate()
对它的引用无效的情况下摆脱映射(及其互斥体)中的某个 Foobar
。有办法实现吗?
我考虑过将所有调用序列化到一个消息队列中,并在内部使用异步回调,使用阻塞等待从外部看起来是同步的。那将是线程安全的并且看起来是同步的,但不会满足第 3 点。
通过在映射中存储指向 Foobar 的共享指针,您可以在 frobnicate 处理它时安全地删除它。
地图:
std::map<int, std::shared_ptr<Foobar> > allFoobars;
代码
int createNewFoobar() {
// First create the Foobar, so a lengthy creation does not hold the mutex
std::shared_ptr<Foobar> newFoobar(std::make_shared<Foobar>());
std::lock_guard<std::mutex> mapLock(mapMutex);
allFoobars[nextId] = newFoobar;
return nextId++;
}
void frobnicate(int foobarId) {
std::map<Foobar>::iterator findFoobar;
{
std::lock_guard<std::mutex> mapLock(mapMutex);
findFoobar = allFobars.find(foobarId);
if (findFoobar == allFoobar.end())
{
return;
}
}
findFoobar.second->frobnicate();
}
然后,即使您从地图中删除 Foobar,findFoobar.second
中的共享指针仍会使其保持活动状态,直到 frobnicate
终止。
我正在设计一个小型图书馆作为玩具项目的一部分。简化后,它会是这样的:
class FoobarManager {
public:
// Creates a new Foobar object internally an returns its ID
int createNewFoobar();
// Makes the Foobar object identified by foobarId frobnicate
void frobnicate(int foobarId);
// Removes the internal Foobar object identified by foobarId
void removeFoobar(int foobarId);
private:
// ID - Foobar
std::map<int, Foobar> allFoobars;
};
我的想法是我可以同时拥有多个 Foobar
,每个都有一个 ID,我可以用它来请求 FoobarManager 来操作它们。我的目标是制作图书馆:
- 线程安全:不假设调用每个方法的线程。
- 同步:我希望
createNewFoobar()
到 return 一个Foobar
,而不是提供一个onFoobarCreated()
回调。 - 从不同
Foobar
对象的角度来看尽可能独立:不要在一个对象 frobnicating 时锁定所有Foobar
对象。
我似乎找不到一个好的锁定模型来满足所有这些要求。我想我至少需要一个 mutex
每个 Foobar
,和另一个 mutex
来控制地图中的 insertion/deletion。
插入新对象与 frobnicate
一起工作似乎很容易:
int createNewFoobar() {
std::lock_guard<std::mutex> mapLock(mapMutex);
allFoobars[++nextId] = Foobar();
return nextId;
}
void frobnicate(int foobarId) {
// Not using lock_guard because we need to intertwine with another lock
mapMutex.lock();
if (allFoobars.count(foobarId) == 0) return;
Foobar& fb = allFoobars.at(foobarId);
// Lock on the Foobar mutex
// ...
mapMutex.unlock();
fb.frobnicate();
// Unlock the Foobar mutex
// ...
}
但是,我无法弄清楚如何在不使 frobnicate()
对它的引用无效的情况下摆脱映射(及其互斥体)中的某个 Foobar
。有办法实现吗?
我考虑过将所有调用序列化到一个消息队列中,并在内部使用异步回调,使用阻塞等待从外部看起来是同步的。那将是线程安全的并且看起来是同步的,但不会满足第 3 点。
通过在映射中存储指向 Foobar 的共享指针,您可以在 frobnicate 处理它时安全地删除它。
地图:
std::map<int, std::shared_ptr<Foobar> > allFoobars;
代码
int createNewFoobar() {
// First create the Foobar, so a lengthy creation does not hold the mutex
std::shared_ptr<Foobar> newFoobar(std::make_shared<Foobar>());
std::lock_guard<std::mutex> mapLock(mapMutex);
allFoobars[nextId] = newFoobar;
return nextId++;
}
void frobnicate(int foobarId) {
std::map<Foobar>::iterator findFoobar;
{
std::lock_guard<std::mutex> mapLock(mapMutex);
findFoobar = allFobars.find(foobarId);
if (findFoobar == allFoobar.end())
{
return;
}
}
findFoobar.second->frobnicate();
}
然后,即使您从地图中删除 Foobar,findFoobar.second
中的共享指针仍会使其保持活动状态,直到 frobnicate
终止。