Правильная модель блокировки для синхронного, многопоточного интерфейса - PullRequest
0 голосов
/ 09 ноября 2018

Я проектирую небольшую библиотеку как часть игрушечного проекта. Упрощенно, это было бы что-то вроде:

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 одновременно, у каждого из них будет идентификатор, который я могу использовать, чтобы попросить 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 голосов
/ 09 ноября 2018

Сохраняя общий указатель на 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 не завершится.

...