Я работаю над продуктом, который обычно создается как общая библиотека.
Приложение-использующее загрузит его, создаст несколько дескрипторов, использует их и, в конечном итоге, освободит все дескрипторы и выгружает библиотеку.
Библиотека создает несколько фоновых потоков, которые обычно останавливаются в момент освобождения дескрипторов.
Теперь проблема в том, что некоторые приложения-потребители плохо себя ведут и в некоторых случаях не могут освободить дескрипторы (отмена, ошибки и т. Д.). В конце концов, статические деструкторы в нашей библиотеке запускаются и дают сбой при попытке взаимодействия с (теперь мертвыми) фоновыми потоками.
Одна из возможностей - не иметь глобальных объектов с деструкторами и, таким образом, избежать запуска любого кода в библиотеке во время статического уничтожения. Это, вероятно, решит сбой при выходе из процесса, но это приведет к утечкам и сбоям в сценарии, когда приложение просто выгружает библиотеку без освобождения дескрипторов (в отличие от выхода), поскольку мы не гарантируем, что фоновые потоки действительно остановились до того, как код, который они выполняли, был выгружен.
Что еще более важно, насколько мне известно, когда main () завершается, все другие потоки будут убиты, где бы они ни находились в то время, что может привести к блокировке блокировок и нарушению инвариантов (например, в диспетчере кучи).
Учитывая это, есть ли смысл пытаться поддерживать эти глючные приложения?
1 ответ
Да, ваша библиотека должна разрешать завершение процесса без предупреждения. Возможно, в идеальном мире каждая программа, использующая вашу библиотеку, будет тщательно отслеживать дескрипторы и освобождать их все, когда она завершается по какой-либо причине, но на практике это нереалистичное требование. Путь кода, запускающий выход программы, может быть общим компонентом, который даже не знает, что ваша библиотека используется!
В любом случае вполне вероятно, что ваша текущая архитектура имеет более общую проблему, потому что статическим деструкторам небезопасно взаимодействовать с другими потоками.
Из точки входа DllMain в MSDN:
Поскольку уведомления DLL сериализуются, функции точки входа не должны пытаться взаимодействовать с другими потоками или процессами. В результате могут возникнуть взаимоблокировки.
А также
Если ваша DLL связана с библиотекой времени выполнения C (CRT), точка входа, предоставляемая CRT, вызывает конструкторы и деструкторы для глобальных и статических объектов C ++. Следовательно, эти ограничения для DllMain также применяются к конструкторам и деструкторам и любому коду, который вызывается из них.
В частности, если ваши деструкторы пытаются дождаться выхода ваших потоков, это почти наверняка зайдет в тупик в случае, когда библиотека явно выгружается, пока потоки все еще работают. Если деструкторы не ждут, процесс выйдет из строя, когда код, выполняемый потоками, исчезнет. Я не уверен, почему вы еще не видите эту проблему; возможно, вы завершаете потоки? (Это тоже небезопасно, хотя и по разным причинам.)
Есть несколько способов решить эту проблему. Наверное, самый простой - это тот, о котором вы уже упоминали:
Одна из возможностей - не иметь глобальных объектов с деструкторами и, таким образом, избежать запуска любого кода в библиотеке во время статического уничтожения.
Вы продолжаете говорить:
[...] но это приведет к утечкам и сбоям в сценарии, когда приложение просто выгружает библиотеку, не освобождая дескрипторы [...]
Это не твоя проблема! Библиотека будет выгружена только в том случае, если приложение явно решит это сделать; очевидно, и в отличие от более раннего сценария, рассматриваемый код знает, что ваша библиотека присутствует, поэтому для вас вполне разумно потребовать, чтобы он закрыл все ваши дескрипторы перед этим.
Однако в идеале вы должны предоставить функцию неинициализации, которая автоматически закрывает все дескрипторы, вместо того, чтобы требовать от приложения закрытия каждого дескриптора по отдельности. Явные функции инициализации и деинициализации также позволяют безопасно настраивать и освобождать глобальные ресурсы, что обычно более эффективно, чем выполнение всех настроек и разборки для каждого дескриптора, и определенно безопаснее, чем использование глобальных ресурсов. объекты.
(См. Ссылку выше для полного описания всех ограничений, применимых к статическим конструкторам и деструкторам; они довольно обширны. Создание всех ваших глобальных объектов в явной подпрограмме инициализации и их уничтожение при явной подпрограмме неинициализации позволяет избежать всего беспорядка. )
Похожие вопросы
Новые вопросы
c++
C++ — это язык программирования общего назначения. Изначально он разрабатывался как расширение C и имел аналогичный синтаксис, но теперь это совершенно другой язык. Используйте этот тег для вопросов о коде, который будет скомпилирован с помощью компилятора C++. Используйте тег версии для вопросов, связанных с конкретной стандартной версией [C++11], [C++14], [C++17], [C++20] или [C++23]. и т.д.