Написание многопоточного исключительного безопасного кода - PullRequest
9 голосов
/ 30 ноября 2008

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

Ответы [ 9 ]

7 голосов
/ 29 января 2009

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 );
}
4 голосов
/ 30 ноября 2008

Я полагаю, что стандарт 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'
2 голосов
/ 30 января 2009

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

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

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

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

T t = q.pop();

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

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

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

2 голосов
/ 01 декабря 2008

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

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

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

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 это создает определенное напряжение между безопасностью потоков и безопасностью исключений. Или, по крайней мере, вам нужно провести дополнительный анализ, поскольку каждый класс, используемый в качестве общего ресурса, должен быть исследован в обоих источниках.

2 голосов
/ 01 декабря 2008

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

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

2 голосов
/ 01 декабря 2008

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

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

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

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

1 голос
/ 01 декабря 2008

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

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

    поймать (...)

без

throw;

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

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

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

0 голосов
/ 30 ноября 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...