Мы создаем приложение для нескольких платформ (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.
Реальный вопрос : Какой безопасный способ решения этой серьезной проблемы синхронизации?