started working thread
got val: 1
got val: 2
got val: 3
dtor completed
Я всю голову сломал почему я не вижу в output "exit working thread" и "got val: last value", но join отработал без проблем ?
Если закоментировать join в деструкторе, то получаю исключение "abort called", что правильно.
Если закоментировать работу с conditional variable в деструцторе, то ничего не меняется, хотя поток не должен был завершиться.
Если это всё скомпилированно в одном модуле (exe), то всё работает правильно, т.е. получаю last value из десттруктора и вижу вывод "exit working thread".
Как так? Надо ли волноваться?
Если вдруг, кто-нибудь захочет поиграться, то вот solution
started working thread
got val: 1
got val: 2
got val: 3
break
got val: last value
exit working thread
dtor completed
Я понимаю, что это связано как-то с убиванием dll рантаймом, но по логике не могу понять как такое происходит. Логика подсказывает, что нужно либо упасть, либо висеть вечно.
Вникать лень но что сказать имею.
В винде _НИЗЗЯ_ ждать завершения потока из DllMain — это вызовет дедлок по неочевидным для простого писателя кода причинам. Это означает в том числе что нельзя ждать завершения потока в деструкторе статик объектов, которые объявлены в длл (так как эти деструкторы исполняются из DllMain).
Как много веселых ребят, и все делают велосипед...
Здравствуйте, Doom100500, Вы писали:
D>Привет эксперты. D>Если бы не dll, то это не было бы в прикладных вопросах, но, если нет dll, то и проблема не воспроизводится.
Можно конечно разбираться что конкретно происходит при выходе (скорее всего deadlock), но в общем случае в DLL Main нельзя использовать функции из других системных DLL.
Вот что написано в документации!
Because DLL notifications are serialized, entry-point functions should not attempt to communicate with other threads or processes. Deadlocks may occur as a result.
The entry-point function should perform only simple initialization or termination tasks. It must not call the LoadLibrary or LoadLibraryEx function (or a function that calls these functions), because this may create dependency loops in the DLL load order. This can result in a DLL being used before the system has executed its initialization code. Similarly, the entry-point function must not call the FreeLibrary function (or a function that calls FreeLibrary) during process termination, because this can result in a DLL being used after the system has executed its termination code.
Здравствуйте, Videoman, Вы писали:
V>Здравствуйте, Doom100500, Вы писали:
V>Можно конечно разбираться что конкретно происходит при выходе (скорее всего deadlock), но в общем случае в DLL Main нельзя использовать функции из других системных DLL. V>Вот что написано в документации! V>
V>Because DLL notifications are serialized, entry-point functions should not attempt to communicate with other threads or processes. Deadlocks may occur as a result.
V>The entry-point function should perform only simple initialization or termination tasks. It must not call the LoadLibrary or LoadLibraryEx function (or a function that calls these functions), because this may create dependency loops in the DLL load order. This can result in a DLL being used before the system has executed its initialization code. Similarly, the entry-point function must not call the FreeLibrary function (or a function that calls FreeLibrary) during process termination, because this can result in a DLL being used after the system has executed its termination code.
V>А все эти вещи могут возникнуть в вашем коде.
Был бы deadlock, было бы понятно, но поток (std::thread) завершается молча, и создаётся впечатление, что всё работает правильно. Только после добавления диагностических логов возник вопрос.
Здравствуйте, Doom100500, Вы писали:
AD>>Всё целиком лень смотреть, но как wait сможет завершится если queue.empty() даже если is_running = false ?
D>Там в деструкторе пихается значение в очередь.
Здравствуйте, ononim, Вы писали:
O>Вникать лень но что сказать имею. O>В винде _НИЗЗЯ_ ждать завершения потока из DllMain — это вызовет дедлок по неочевидным для простого писателя кода причинам. Это означает в том числе что нельзя ждать завершения потока в деструкторе статик объектов, которые объявлены в длл (так как эти деструкторы исполняются из DllMain).
Ага.
Общее правило, которое я выработал для себя:
У DLL должны быть функции инициализации и деинициализации. Можно (лучше) с глобальным счетчиком вызовов.
При обнулении этого счетчика, DLL должна прекратить все свои фоновые потоки.
---
Можно обойтись без этих функций.
Тогда DLL должна предоставлять объекты, которые (автоматически) рулят этим глобальным счетчиком использования DLL.
---
Частный пример — DLL сервер COM объектов.
При освобождении последнего COM-объекта, DLL должна прекращать все свои фоновые потоки. Это делается вне DllMain
---
Кстати, если я все правильно помню, у Рихтера еще в третьем издании (это ~98 год) было описана эта проблема, которую он отважно победил
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Здравствуйте, Doom100500, Вы писали:
D>Был бы deadlock, было бы понятно, но поток (std::thread) завершается молча, и создаётся впечатление, что всё работает правильно. Только после добавления диагностических логов возник вопрос.
Ну я подебажил ваш код. Так и есть, после выхода из main процесса начинает разрушаться CRT и там уже зовется DllMain из него. Т.е. ресурсы уже начинают отлетать потихоньку, а вы ими продолжаете пользоваться в DLL, тем же std::out и т.д. Все что нужно сделать это вызвать release для w перед выходом из main и все начинает работать как вы хотите.
Здравствуйте, Videoman, Вы писали:
V>Ну я подебажил ваш код. Так и есть, после выхода из main процесса начинает разрушаться CRT и там уже зовется DllMain из него. Т.е. ресурсы уже начинают отлетать потихоньку, а вы ими продолжаете пользоваться в DLL, тем же std::out и т.д. Все что нужно сделать это вызвать release для w перед выходом из main и все начинает работать как вы хотите.
Спасибо.
Это именно тот вывод, который я сделал после Вашего и ononim-ного ответа. Решается ручным управлением времени жизни из вызывающей стороны (тогда unque_ptr тоже не имеет смысла).
Я тоже когда-то Рихтера читал, только забыл уже.