Отсутствие блокировки при уведомлении переменной условия межпроцессорной обработки вызывает проблемы

ОБНОВЛЕНИЕ 21.02.2020: Удерживание блокировки во время уведомления на самом деле не помогает. Насколько я понимаю, переменная условия остается недействительной в общей памяти ожидающим процессом.

Итак, у меня есть это приложение, использующее ускоренный межпроцессный процесс для совместного использования памяти, и доступ к нему синхронизируется с использованием переменной межпроцессного условия. Я использую Boost 1.62 в Windows. Я компилирую с помощью Microsoft Windows Build Tools 2015.

Что происходит, так это то, что когда я завершаю процесс ожидания с помощью Ctrl-C, процесс уведомления застревает в вызове уведомления.

Вот демонстрационная программа, которая позволяет воспроизвести проблему. Вы должны запустить исполняемый файл один раз без каких-либо аргументов, чтобы запустить процесс ожидания, и еще раз с некоторым аргументом, чтобы запустить процесс уведомления. Затем убейте первый процесс. Иногда вы можете заметить, что печать останавливается на «Входящем уведомлении».

#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/interprocess/sync/interprocess_condition.hpp>

#include <iostream>

struct shared_data
{
   boost::interprocess::interprocess_mutex mutex;
   boost::interprocess::interprocess_condition condition;

   bool test_bool = false;
};


int main(int argc, char *argv[])
{
    using namespace boost::interprocess;

    if (argc == 1) {
        struct shm_remove
        {
            shm_remove() {
                shared_memory_object::remove("MySharedMemory");
            }
            ~shm_remove() {
                shared_memory_object::remove("MySharedMemory");
            }
        } remover;

        shared_memory_object shm(create_only, "MySharedMemory", read_write);

        shm.truncate(sizeof(shared_data));
        mapped_region region(shm, read_write);
        void* addr = region.get_address();
        shared_data* data = new (addr) shared_data;

        while (true) {
            scoped_lock<interprocess_mutex> lock(data->mutex);
            while (!data->test_bool) {
                data->condition.wait(lock);
            }
            std::cout << "test_bool became true" << std::endl;
            data->test_bool = false;
        }
    }
    else {
        shared_memory_object shm(open_only, "MySharedMemory", read_write);
        mapped_region region(shm, read_write);
        shared_data* data = static_cast<shared_data*>(region.get_address());
        while (true) {
            {
                scoped_lock<interprocess_mutex> lock(data->mutex);
                data->test_bool = true;
            }
            std::cout << "Entering notify" << std::endl;
            data->condition.notify_one();
            std::cout << "Exiting notify" << std::endl;
        }
    }
}

(Конечно, убийство во время ожидания сурово, но, насколько я отладил, вызов ожидания очищается после сигнала)

Если я сохраню полученную блокировку при вызове notify_one, проблема не проявится. Однако я ожидал, что не будет необходимости сохранять полученную блокировку при уведомлении в духе реализации потоковой передачи C ++. Я не нашел никакой спецификации по этому поводу в документации, только example, который действительно сохраняет полученную блокировку.

Теперь, учитывая, что у меня есть решение моей проблемы, мои вопросы таковы:

  1. Есть ли необходимость в получении блокировки при уведомлении об ожидаемом и единственно правильном использовании, или это ошибка?
  2. Если это ожидаемое использование, почему?

person ianos    schedule 30.01.2020    source источник


Ответы (1)


Вам не обязательно удерживать блокировку при вызове notify, но в большинстве случаев вы все равно должны это делать, потому что в противном случае некоторые потоки (или в вашем случае процессы) могут пропустить уведомление. Рассмотрим следующий сценарий:

  • процесс 1 получает блокировку и проверяет условие, но выгружается перед вызовом condition.wait
  • процесс 2 вызывает condition.notify_one - но нет процесса, который нужно уведомить
  • вы убиваете процесс 2
  • теперь процесс 1 наконец вызывает condition.wait - и ждет вечно.

Установив блокировку перед вызовом notify, вы можете гарантировать, что другой процесс уже вызвал wait и, следовательно, не может пропустить уведомление. Это также относится к std::condition_variable, а не только к вашему примеру межпроцессного взаимодействия.

Есть несколько ситуаций, в которых это может не быть проблемой (например, потому что вы не ждете вечно, а только в течение ограниченного времени), но вам следует быть очень осторожными в этом случае.

person mpoeter    schedule 31.01.2020
comment
Я вижу, что бывают случаи, когда нужно удерживать блокировку при уведомлении. Но есть ситуации, когда вам не нужно, и я думаю, что мой пример - один из них. Тем не менее, я считаю неправильным поведение блокировки вызова уведомления при вызове без блокировки (в сценарии выше). Остается вопрос: это ошибка или какое-то ограничение библиотеки? - person ianos; 31.01.2020