同步线程安全接口的正确锁定模型

时间:2018-11-09 09:20:13

标签: c++ multithreading locking

我正在设计一个小型图书馆,作为玩具项目的一部分。简化后,它将类似于:

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,我可以使用该ID要求FoobarManager对其进行操作。我的目标是制作图书馆:

  1. 线程安全:不对每个方法从哪个线程调用进行任何假设。
  2. 同步:我希望createNewFoobar()返回Foobar,而不提供onFoobarCreated()回调。
  3. 从不同的Foobar对象的角度看,应尽可能独立:当一个对象处于混乱状态时,不要锁定所有Foobar对象。

我似乎找不到一个很好的锁定模型来满足所有要求。我猜我每个mutex至少需要一个Foobar,另外一个mutex才能控制地图中的插入/删除。

插入新对象与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
    // ...
}

但是,我无法弄清楚如何在不使Foobar对其引用无效的情况下摆脱地图中的某个frobnicate()(及其互斥体)。有没有办法做到这一点?

我考虑过将所有调用序列化为消息队列,并在内部使用异步回调,并使用阻塞等待从外部看起来是同步的。那将是线程安全的,并且看起来是同步的,但不会满足第3点的要求。

1 个答案:

答案 0 :(得分:1)

通过在地图中存储指向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终止。