Написание многопоточного кода, защищенного от исключений

StackOverflow https://stackoverflow.com/questions/329061

Вопрос

Каковы противоречия между многопоточностью и безопасностью исключений в C++?Существуют ли хорошие рекомендации, которым следует следовать?Завершается ли поток из-за неперехваченного исключения?

Это было полезно?

Решение

Я полагаю, что стандарт C ++ не упоминает о многопоточности - многопоточность - это особенность платформы.

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

В быстром и грязном тесте, который я сделал с g ++ 4.0.1 (точнее, i686-apple-darwin8-g ++ - 4.0.1), результат terminate () называется, который убивает всю программу. Код, который я использовал, следующий:

#include <stdio.h>
#include <pthread.h>

void *threadproc(void *x)
{
  throw 0;

  return NULL;
}

int main(int argc, char **argv)
{
  pthread_t t;
  pthread_create(&t, NULL, threadproc, NULL);

  void *ret;
  pthread_join(t, &ret);

  printf("ret = 0x%08x\n", ret);

  return 0;
}

Скомпилировано с помощью g ++ threadtest.cc -lpthread -o threadtest . Вывод был:

terminate called after throwing an instance of 'int'

Другие советы

C ++ 0x будет иметь язык Поддержка транспортировки исключений между потоками , чтобы при возникновении исключения рабочий поток порождал поток, который мог его перехватить или перебросить.

Из предложения:

namespace std {

    typedef unspecified exception_ptr;

    exception_ptr current_exception();
    void rethrow_exception( exception_ptr p );

    template< class E > exception_ptr copy_exception( E e );
}

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

Даже если вы переопределяете terminate_handler по умолчанию по умолчанию, стандарт говорит, что подпрограмма, которую вы предоставляете " должна прервать выполнение программы, не возвращаясь к вызывающей стороне " (ISO 14882-2003 18.6.1.3).

Итак, в итоге, необработанное исключение завершит программу, а не только поток.

Что касается безопасности потоков, Адам Розенфилд говорит, что специфичная для платформы вещь, которая не рассматривается стандартом.

Это единственная главная причина существования Эрланга.

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

Как уже обсуждали другие, параллелизм (и в особенности безопасность потоков) - это архитектурная проблема, которая влияет на то, как вы проектируете свою систему и свое приложение.

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

На уровне класса потокобезопасность требует изменений в интерфейсе. Так же, как исключение безопасности. Например, классы обычно возвращают ссылки на внутренние переменные, например:

class Foo {
public:
  void set_value(std::string const & s);

  std::string const & value() const;
};

Если Foo используется несколькими потоками, вас ждут проблемы. Естественно, вы можете поставить мьютекс или другую блокировку для доступа к Foo. Но довольно скоро все программисты на C ++ захотят заключить Foo в «ThreadSafeFoo». Я утверждаю, что интерфейс для Foo должен быть изменен на:

class Foo {
public:
  void set_value(std::string const & s);

  std::string value() const;
};

Да, это дороже, но его можно сделать поточно-ориентированным с помощью замков внутри Foo. IMnsHO это создает определенное напряжение между безопасностью потоков и безопасностью исключений. Или, по крайней мере, вам нужно провести дополнительный анализ, поскольку каждый класс, используемый в качестве общего ресурса, должен быть исследован в обоих источниках.

Один классический пример (не помню, где я его впервые увидел) - в библиотеке std.

Вот как вы извлекаете что-то из очереди:

T t;
t = q.front(); // may throw
q.pop();

Этот интерфейс несколько тупой по сравнению с:

T t = q.pop();

Но это сделано, потому что T-копия может бросить. Если копия выбрасывается после появления всплывающего окна, этот элемент теряется из очереди и не может быть восстановлен. Но поскольку копирование происходит до извлечения элемента, вы можете поместить произвольную обработку вокруг копии из front () в блоки try / catch.

Недостатком является то, что вы не можете реализовать очередь, которая является поточно-ориентированной, с интерфейсом std :: queue из-за двух задействованных шагов. То, что хорошо для безопасности исключений (разделяя шаги, которые можно выполнить), теперь плохо для многопоточности.

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

Я заметил две проблемы:

  • в g++ в Linux уничтожение потока (pthread_cancel) осуществляется путем создания «неизвестного» исключения.С одной стороны, это позволяет вам хорошо очистить поток при уничтожении.С другой стороны, если вы перехватите это исключение и не создадите его повторно, ваш код завершится функцией abort().Следовательно, если вы или какая-либо из библиотек, которые вы используете, уничтожаете потоки, вы не можете

    ловить(...)

без

throw;

в вашем резьбовом коде. Здесь является ссылкой на такое поведение в сети:

  • Иногда вам нужно передать исключение между потоками.Это непростая задача — в конечном итоге мы применили кое-какой хак, когда правильным решением является тот тип маршаллинга/демаршаллинга, который вы используете между процессами.

Я не рекомендую оставлять какое-либо исключение невредимым. Оберните ваши потоковые функции верхнего уровня в обработчики catch-all, которые могут более изящно (или, по крайней мере, многословно) завершить работу программы.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top