Отдельные потоки, обращающиеся к глобальным или статическим объектам - PullRequest
0 голосов
/ 22 января 2019

Следующий текст является выдержкой из раздела 18.2.1 книги под названием Стандартная библиотека C ++: учебное пособие и справочник, 2-е издание :

Обратите внимание, однако, что проблема времени жизни также относится к глобальным и статическим объектам, потому что при выходе из программы отсоединенный поток может все еще работать, что означает, что он может получить доступ к глобальным или статическим объектам, которые уже уничтожены или находятся подразрушение.К сожалению, это может привести к неопределенному поведению.

Насколько я понимаю, все отключенные потоки будут прерваны, когда закончится main().

Поэтому я подозреваю, что причина такого поведения в том, что фактический порядок уничтожения глобальных и статических объектов не указан в отношении завершения отсоединенных потоков, т. Е. Это может произойтидо, во время или после завершения отдельных потоков.

Будем признательны за любые дальнейшие разъяснения по этому вопросу.


Более конкретно: в подразделе под заголовком Остерегайтесь отдельных нитей .

1 Ответ

0 голосов
/ 22 января 2019

Все, что статически инициализировано или лениво инициализировано (например, статическая переменная области блока, чей содержащий блок был введен), деинициализируется во время обычного завершения программы - либо путем возврата main(), либо после вызова exit().

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

Практически говоря, для реализации было бы действительно трудно принудительно завершить потоки - отсоединеноили иным образом.Помимо всего прочего, это рецепт непредсказуемого поведения, поскольку эти потоки почти всегда блокируются объектами синхронизации или системными вызовами и удерживают ресурсы (привет взаимоблокировки!).Во-вторых, Posix-Threads не предоставляет API для этого.Не удивительно, что потоки должны вернуться из своей функции потока, чтобы выйти.

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

Если какой-либо из этих потоков получает доступ к статически инициализированному объекту, это, конечно, неопределенное поведение .

Недавно я потратил немало времени, чтобы отследить серию сбоев в большом приложении для iOS с большим количеством C ++.

Код сбоев выглядел примерно так, с падением глубоко в недрахиз std::set<T>::find(const T&)


bool checkWord(const std::string &w)
{
    static std::set<std::string> tags{"foo", "bar"};
    return (tags.find(w) != tags.end());
}


Между тем, в главном потоке был вызов exit() нескольких функций в стеке.

Приложения для iOS и macOS сильно многопоточныеиспользуя Grand Central Dispatch / libdispatch, и оказывается, что потоки не только продолжают работать после выхода main(), но и выполняются задания из фоновых очередей отправки.

Я подозреваю, что подобная ситуация будетмногие другие системы.

Я не нашел ужасно хорошего решения этой проблемы, кроме как избежать статических вычислений в блоке в пользу данных, которые не требуют инициализации.

...