С++ SIGABRT в деструкторе структуры в многопоточном приложении

Мне понадобится помощь в решении сегодняшней загадки многопоточности.

У меня есть приложение с посредником, скажем:

class Mediator{
    ConfigMgr * mgr;
    ....
    Config getConfig(){
    return mgr->getConfig();
    };
};

ConfigMgr правильно инициализирован, с ним проблем нет. Он состоит, среди прочего, из схемы конфигурации, которая представляет собой структуру с некоторыми логическими значениями и std::strings:

struct Config{
    std::string param1;
    std::string param2;
    ....
}

class ConfigMgr {
    Config blueprint;

    Config getConfig(){
        Lock l(mtx); //wrapper on POSIX mutex and lock, works as expected
        refreshConfig(); // some stuff that might alter Config blueprint
        return config;
    }

}

Наконец, у меня есть несколько рабочих потоков, которым время от времени может понадобиться вызвать mediator->getConfig().param1;.

Проблема в том, что время от времени мое приложение вылетает из-за SIGABRT. Из того, что я смог определить, происходит сбой при двойном удалении строки в деструкторе Config: Config::~Config() В структуре нет написанных мной методов.

Я не могу отследить первопричину. Моя структура конфигурации всегда передается копией, а не ссылкой. Я думаю, что каждый поток должен иметь свою собственную копию Config с момента вызова ConfigMgr::getConfig(). Эта конструкция должна быть потокобезопасной, но явно существует какое-то состояние гонки. У вас есть какие-нибудь советы?


person telpeloth    schedule 18.12.2012    source источник
comment
Возможно ли, что экземпляр ConfigMgr, владеющий объектом Config, удаляется дважды?   -  person Ben Voigt    schedule 18.12.2012
comment
Является ли getConfig единственным API из ваших рабочих потоков для ConfigMgr?   -  person SomeWittyUsername    schedule 18.12.2012
comment
@BenVoigt: ConfigMgr не удаляется. Я имею в виду, что он удаляется только во время закрытия программы, его нельзя удалить во время выполнения. icepack: на самом деле, у configMgr есть еще три метода, которые могут вызывать потоки, все они защищены с помощью блокировки/мьютекса и все возвращают единственную строку или целое число. Я пропустил это, так как я, хотя это не имеет значения.   -  person telpeloth    schedule 18.12.2012


Ответы (1)


Я был поражен этим. Важно помнить:

  1. std::string разделяет свой буфер char* при копировании.
  2. std::string не предназначен для многопоточности.

Поэтому вы сталкиваетесь с несколькими std::strings в разных потоках, пытаясь удалить один и тот же буфер, который, по их мнению, должен быть удален из-за неправильного обновления переменных подсчета ссылок.

Вам, вероятно, придется более подробно указать нужные копии строк и определить конструктор копирования для объекта Config.

person Douglas Leeder    schedule 18.12.2012
comment
Вы имеете в виду, что если у вас есть глобальный строковый объект, который назначается во время запуска (пока процесс является однопоточным), а затем несколько потоков делают его копии (но ничего не вносит изменений в экземпляр общей строки), то это небезопасно? Это ужасно. - person Ben Voigt; 18.12.2012
comment
@BenVoigt Я проверил первый пункт, используя valgrind и копии строк, второй очевиден, учитывая историю многопоточности в C ++. (Это означает, что некоторые реализации могут быть потокобезопасными, но это не обязательно). - person Douglas Leeder; 18.12.2012
comment
@BenVoigt Когда строка копируется в два потока одновременно, они могут недостаточно увеличить счетчик ссылок, тогда буфер может быть освобожден раньше (что позволяет ссылаться на освобожденную память), аналогично, если несколько потоков одновременно освобождают копии строк. раз они могут многократно пытаться освободить буфер - AFAIK. - person Douglas Leeder; 18.12.2012
comment
Да, я просмотрел Стандарт, и в нем явно не указано, разрешено ли немутирующим функциям изменять области общей памяти. В текущем состоянии неспецификации даже библиотеку потоков не всегда можно использовать в нескольких потоках. Определенно то, что, я надеюсь, они решат в будущем. - person Ben Voigt; 18.12.2012
comment
Спасибо за ответ, это очень помогло. Я решил проблему, создав конструктор копирования для структуры конфигурации, которую я защитил с помощью мьютекса, и я заверил, что буфер воссоздан, а не разделен. - person telpeloth; 15.01.2013