Отмена потока, который заблокирован мьютексом, не разблокирует мьютекс

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

Случается в Red Hat и Oracle Enterprise Linux 5.7 (и 5.8), приложение написано на С++

Проблема, с которой они столкнулись, заключается в том, что основной поток запускает отдельный поток для создания потенциально долговременного подключения TCP connect() [client to server]. Если "длительный" аспект занимает слишком много времени, они отменяют поток и запускают другой.

Это делается потому, что мы не знаем состояние серверной программы:

  • серверная программа работает → соединение немедленно принято
  • серверная программа не работает, машина и сеть в порядке → соединение немедленно не удалось с ошибкой "connection отказано"
  • машина или сеть сбой или сбой → соединение занимает много времени с ошибкой "нет маршрута к хосту"

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

Это оставляет основной поток, зависящий от попытки блокировки мьютекса.

Подробная информация об окружающей среде:

  • Glibc-2.5-65
  • Glibc-2.5-65
  • libcap-1.10-26
  • ядро-отладки 2.6.18-274.el5
  • Glibc-заголовков-2.5-65
  • Glibc-синфазный 2.5-65
  • libcap-1.10-26
  • ядро-док-2.6.18-274.el5
  • ядро-2.6.18-274.el5
  • ядро-заголовков-2.6.18-274.el5
  • Glibc-разви-2.5-65

Код был создан с: С++ -g3 tst2.C -lpthread -o tst2

Приветствуются любые советы и рекомендации

Ответ 1

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

Использование RAII для разблокировки мьютекса будет более надежным. В GNU/Linux pthread_cancel реализовано специальное исключение типа __cxxabi::__forced_unwind, поэтому, когда поток отменяется, генерируется исключение, и стек разматывается. Если мьютекс заблокирован по типу RAII, то его деструктор будет гарантированно выполняться, если стек разматывается с помощью исключения __forced_unwind. Boost Thread предоставляет переносимую библиотеку С++, которая обертывает Pthreads и намного проще в использовании. Он предоставляет тип RAII boost::mutex и другие полезные абстракции. Boost Thread также предоставляет свой механизм "прерывания потока", который похож на отмену Pthread, но не то же самое, а точки отмены Pthread (например, connect) не являются точками прерывания потока Boost, что может быть полезно для некоторых приложений. Однако в случае вашего клиента с момента отмены прерывания вызова connect они, вероятно, хотят придерживаться отмены Pthread. (Не переносной) способ GNU/Linux реализует отмену как исключение, он будет хорошо работать с boost::mutex.

На самом деле нет оправдания явно блокировки и разблокировки мьютексов, когда вы пишете на С++, IMHO самая важная и самая полезная функция С++ - это деструкторы, которые идеально подходят для автоматического выпуска ресурсов, таких как блокировки мьютекса.

Другим вариантом будет использование надежного мьютекса, который создается путем вызова pthread_mutexattr_setrobust на pthread_mutexattr_t перед инициализацией мьютекса. Если нить умирает при сохранении надежного мьютекса, ядро ​​его запомнит, так что следующий поток, который пытается заблокировать мьютекс, получает специальный код ошибки EOWNERDEAD. Если возможно, новый поток может снова защитить данные, защищенные потоком, и взять на себя ответственность за мьютекс. Это гораздо труднее использовать правильно, чем просто использовать тип RAII для блокировки и разблокировки мьютекса.

Совершенно другой подход состоял бы в том, чтобы решить, действительно ли вам нужно удерживать блокировку мьютекса при вызове connect. Удержание мьютексов во время медленных операций - это не очень хорошая идея. Не можете ли вы позвонить connect, а затем, если удалите блокировку мьютекса и обновите защищенные общедоступные данные с помощью мьютекса?

Мое предпочтение было бы в том, чтобы использовать Boost Thread и избегать удержания мьютекса в течение длительных периодов времени.

Ответ 2

Проблема, с которой они столкнулись, заключается в том, что основной поток запускает отдельный поток для создания потенциально долговременного подключения TCP connect() [client to server]. Если "длительный" аспект занимает слишком много времени, они отменяют поток и запускают другой.

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

Кроме того, поток не должен содержать мьютекс, ожидая сетевого ввода-вывода. Мьютексы должны использоваться только для вещей, которые бывают быстрыми и, главным образом, ограниченными ЦП или могут быть ограничены локальным диском.

Наконец, если вы чувствуете, что вам нужно проникнуть снаружи и заставить поток что-то сделать, откиньтесь назад. Вы написали код для этого потока. Если вы чувствуете, что это необходимо, это означает, что вы не закодировали этот поток, чтобы делать то, что вы действительно хотели. Исправление состоит в том, чтобы изменить поток, чтобы делать то, и только то, что вы действительно хотите. Тогда вам не придется "выталкивать" его снаружи.