Как безопасно разделить объект между процессами, используя boost interprocess (ipc)? - PullRequest
0 голосов
/ 10 апреля 2019

Мы создаем приложение для нескольких платформ (macOS и Windows на данный момент). Одно из указанных действий заключается в том, что, если несколько экземпляров приложения запускаются в одной среде (ядре), все они будут иметь доступ к общему объекту, таким образом помещаясь в общую память. Существует синхронизация для получения гарантии DRF при доступе к данным, которые мы хотим предоставить.

Boost.Interprocess появилась как хорошая библиотека для удовлетворения этого требования. В частности, скажем, мы разделяем этот объект между несколькими процессами:

namespace bi = boost::interprocess;
struct SharedObject
{
    int value;
    std::string anotherValue;
    // Synchronization mechanism
    bi::interprocess_mutex mutex; //synchronization
    int processCount{0};
};

Теперь, следуя примерам , можно поместить такой объект в разделяемую память следующим образом:

// Create a shared memory region of the correct size
bi::shared_memory_object shm{bi::create_only, "my_shared_memory", bi::read_write};
shm.truncate(sizeof(SharedObject));

//Map the whole shared memory in this process
bi::mapped_region region(shm, bi::read_write);

// Calls SharedObject ctor on the shared memory allocated for it
SharedObject *shared = new (region.get_address()) SharedObject;
{
    bi::scoped_lock<bi::interprocess_mutex> lock(shared->mutex)
    ++shared->processCount;
}

Теперь мы также хотим, чтобы последний процесс оставил чистую среду, то есть без выделения общей памяти, то есть где-то (в объекте защиты):

{
    shared->mutex->lock();
    --shared->processCount;
    if (shared->processCount == 0)
    {
        shared->~SharedObject();
        bi::shared_memory_object::remove("my_shared_memory");
    }
}

Первый вопрос : Мы предполагаем, что безопасно вызывать деструктор заблокированного bi::interprocess_mutex, мы не правы?

Ситуация с курицей и яйцом

Осложнения возникают для последующих процессов. Давайте предположим, что у нас есть функция checkMemoryExist(const std::string &aMemoryName) (к сожалению, она должна быть реализована путем перехвата исключения, но это уже другая тема). Процесс может использовать это, чтобы проверить, была ли память уже распределена, и в ситуации получить это с bi::shared_memory_object shm{bi::open_only, "my_shared_memory", bi::read_write};. Также не нужно создавать объект.

И тут возникает проблема, если предположить, что process1 запущен до process2 :

  • process2 не может получить доступ к переменным синхронизации до получения дескриптора в общей памяти.
  • После того, как process2 получил уже созданную разделяемую память, но перед тем, как заблокировать и увеличить счетчик процессов, process1 выполнит код очистки, уничтожив таким образом объект и освободив память.
  • Затем process2 начинает манипулировать общей памятью, которая теперь является UB.

Реальный вопрос : Какой безопасный способ решения этой серьезной проблемы синхронизации?

...