Синхронизация C и обработка исключений в перекрестных потоках

#c #exception #synchronization #cloning

#c #исключение #синхронизация #клонирование

Вопрос:

Я использую библиотеку boost для обработки потоков и синхронизации в своем приложении.

Прежде всего, я должен сказать, что исключения внутри потоков при синхронизации — это для меня что-то компилируемое. В любом случае ниже приведен псевдокод, которого я хочу достичь. Я хочу, чтобы синхронизированные потоки выдавали то же исключение, которое, ВОЗМОЖНО, было выдано из потока, выполняющего notify . Как я могу этого добиться?

Не удалось найти какие-либо темы из Stack Overflow, касающиеся генерирования исключений при взаимодействии между потоками с использованием модели boost threading

Заранее большое спасибо!

 // mutex and scondition variable for the problem
mutable boost::mutex conditionMutex;
mutable boost::condition_variable condition;

inline void doTheThing() const {

   if (noone doing the thing) {
      try {
          doIt()
          // I succeeded
          failed = false;
          condition.notify_all();
      }
      catch (...) {
          // I failed to do it
          failed = true;
          condition.notify_all();
          throw
      }
  else {
      boost::mutex::scoped_lock lock(conditionMutex);
      condition.wait(lock);
      if (failed) {
          // throw the same exception that was thrown from 
          // thread doing notify_all
       }
  }
  

}

Ответ №1:

Итак, вы хотите, чтобы первый поток, который попадет в цель, doTheThing() вызывал doIt() , а все последующие потоки, которые попадут в цель, doTheThing() ждали завершения вызова первого потока doIt() , прежде чем продолжить.

Я думаю, это должно сработать:

 boost::mutex conditionMutex; // mutable qualifier not needed
bool failed = false;
bool done = false;

inline void doTheThing() const {

   boost::unique_lock uql(conditionMutex);

   if (!done) {
       done = true;
       try {
           doIt();
           failed = false;
       }
       catch (...) {
           failed = true;
           throw
       }
   }
   else if (failed)
   {
       uql.unlock();
       // now this thread knows that another thread called doIt() and an exception 
       // was thrown in that thread.
   }

}
  

Важные примечания:

Каждый вызывающий поток doTheThing() должен получать блокировку. Обойти это невозможно. Вы синхронизируете потоки, и для того, чтобы поток знал что-либо о том, что происходит в другом потоке, он должен получить блокировку. (Или он может использовать операции с атомной памятью, но это более продвинутый метод.) Переменные failed и done защищены conditionMutex .

C вызовет деструктор uql при обычном завершении функции или при выбрасывании исключения.

РЕДАКТИРОВАТЬ О, и что касается передачи исключения всем другим потокам, забудьте об этом, это почти невозможно, и это не то, как все делается в C . Вместо этого каждый поток может проверить, успешно ли вызван первый поток doIt() в указанном выше месте.

РЕДАКТИРОВАТЬ Нет языковой поддержки для распространения исключения в другой поток. Проблему распространения исключений в другой поток можно обобщить на передачу сообщений в другой поток. Существует множество библиотечных решений проблемы передачи сообщений между потоками ( boost::asio::io_service::post() ), и вы могли бы передать сообщение, содержащее исключение, с инструкциями по созданию этого исключения при получении сообщения. Однако это плохая идея. Исключения генерируются только при возникновении ошибки, которая не позволяет вам развернуть стек вызовов с помощью обычного возврата функции. Вот что такое исключение — альтернативный способ возврата из функции, когда возврат обычным способом не имеет смысла.

Комментарии:

1. Спасибо за ответ. Я знаю, что мне нужно заблокировать оператор if, чтобы пропустить другие. Я просто исключил блокировку из псевдокода. В вашем решении требуется, чтобы каждый поток проходил через doIt(). Я хочу, чтобы заблокированные потоки ждали, пока рабочий поток завершит то, что он делает. Реальная проблема заключалась в том, как распространить возможное исключение на синхронизированные потоки. Так вы хотите сказать, что это вообще невозможно?

2. @Lauri 1 из 2: Посмотрите внимательно, проходит только первый поток doIt() , остальные потоки будут пропущены doIt() после проверки done .

3. @Lauri 2 из 2: Я уточнил свой ответ, см. Выше.

4. Если вы используете текущую версию C (C 11), существует языковая поддержка для распространения исключений на другие потоки. Используйте std::current_exception(), чтобы получить объект std::exception_ptr, перенести его в другой поток, а затем вызвать для него std::rethrow_exception(). Или вы можете использовать std::promise /std::future для этого.