Как можно проверить несколько потоков на наличие исключений?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Обработка исключений в многопоточной программе
Это критически важный аспект: исключение, выброшенное в одном потоке, НЕ автоматически попадает в основной поток. Нужна специальная обработка.
Проблема
#include <thread>
#include <iostream>
int main() {
std::thread t([]() {
throw std::runtime_error("Ошибка в потоке!");
});
t.join(); // Здесь программа упадёт с std::terminate
return 0;
}
Почему падает? Потому что исключение выброшено в контексте потока, а не главной программы.
Решение 1: exception_ptr и rethrow
Самый надёжный способ — поймать исключение в потоке, сохранить его указатель и пробросить в основном потоке.
#include <thread>
#include <exception>
#include <iostream>
class ThreadGuard {
private:
std::thread t;
std::exception_ptr eptr;
public:
template<typename Function>
ThreadGuard(Function f) {
t = std::thread([this, f]() {
try {
f();
} catch (...) {
eptr = std::current_exception();
}
});
}
~ThreadGuard() {
if (t.joinable()) {
t.join();
if (eptr) {
std::rethrow_exception(eptr);
}
}
}
};
int main() {
try {
ThreadGuard t([]() {
throw std::runtime_error("Ошибка в потоке!");
});
} catch (const std::exception& e) {
std::cout << "Поймали исключение: " << e.what() << std::endl;
}
return 0;
}
Решение 2: Несколько потоков с exception_ptr
Для нескольких потоков используем вектор exception_ptr.
#include <thread>
#include <vector>
#include <exception>
#include <iostream>
class ThreadPool {
private:
std::vector<std::thread> threads;
std::vector<std::exception_ptr> exceptions;
std::mutex mutex;
public:
template<typename Function>
void add_task(Function f) {
threads.emplace_back([this, f]() {
try {
f();
} catch (...) {
std::lock_guard<std::mutex> lock(mutex);
exceptions.push_back(std::current_exception());
}
});
}
void wait_for_completion() {
for (auto& t : threads) {
if (t.joinable()) {
t.join();
}
}
}
void check_exceptions() {
for (const auto& eptr : exceptions) {
if (eptr) {
try {
std::rethrow_exception(eptr);
} catch (const std::exception& e) {
std::cout << "Исключение: " << e.what() << std::endl;
}
}
}
if (!exceptions.empty()) {
throw std::runtime_error("Несколько потоков завершились с ошибками");
}
}
};
int main() {
ThreadPool pool;
pool.add_task([]() { std::cout << "Поток 1" << std::endl; });
pool.add_task([]() { throw std::runtime_error("Ошибка в потоке 2"); });
pool.add_task([]() { throw std::runtime_error("Ошибка в потоке 3"); });
pool.wait_for_completion();
try {
pool.check_exceptions();
} catch (const std::exception& e) {
std::cout << "Обработано: " << e.what() << std::endl;
}
return 0;
}
Решение 3: Promise и Future
Модернизированный подход с использованием асинхронного программирования.
#include <thread>
#include <future>
#include <vector>
#include <iostream>
int main() {
std::vector<std::future<void>> futures;
// Запускаем задачи через std::async
futures.push_back(std::async(std::launch::async, []() {
std::cout << "Задача 1" << std::endl;
}));
futures.push_back(std::async(std::launch::async, []() {
throw std::runtime_error("Ошибка в задаче 2");
}));
futures.push_back(std::async(std::launch::async, []() {
throw std::logic_error("Логическая ошибка в задаче 3");
}));
// Проверяем результаты
for (size_t i = 0; i < futures.size(); ++i) {
try {
futures[i].get(); // Блокирует и пробрасывает исключение если было
std::cout << "Задача " << i << " успешно завершена" << std::endl;
} catch (const std::runtime_error& e) {
std::cout << "Runtime error в задаче " << i << ": " << e.what() << std::endl;
} catch (const std::logic_error& e) {
std::cout << "Logic error в задаче " << i << ": " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cout << "Общая ошибка в задаче " << i << ": " << e.what() << std::endl;
}
}
return 0;
}
Решение 4: Callback функции
Для более сложных сценариев используйте callback для обработки ошибок.
#include <thread>
#include <functional>
#include <exception>
class AsyncTask {
private:
std::function<void()> on_error;
public:
void set_error_handler(std::function<void(std::exception_ptr)> handler) {
on_error = handler;
}
void run(std::function<void()> task) {
std::thread t([this, task]() {
try {
task();
} catch (...) {
if (on_error) {
on_error(std::current_exception());
}
}
});
t.detach(); // Асинхронное выполнение
}
};
int main() {
AsyncTask task;
task.set_error_handler([](std::exception_ptr eptr) {
try {
std::rethrow_exception(eptr);
} catch (const std::exception& e) {
std::cout << "Ошибка обработана callback: " << e.what() << std::endl;
}
});
task.run([]() {
throw std::runtime_error("Ошибка!");
});
return 0;
}
Сравнение подходов
std::async + future — рекомендуется для новых проектов. Самый безопасный и удобный.
exception_ptr — когда нужна большая гибкость в управлении потоками.
promise/future — когда нужна связь "потребитель-производитель".
Callback — когда обработка ошибок происходит асинхронно.
Основные правила
1. НИКОГДА не игнорируйте исключения в потоках
// ПЛОХО!
std::thread t([]() { throw std::exception(); });
t.join();
// Программа упадёт!
2. ВСЕГДА оборачивайте код в try-catch
// ХОРОШО!
std::thread t([]() {
try {
// ваш код
} catch (...) {
// сохраняем исключение
}
});
3. Используйте std::async для простых случаев
// Проще и безопаснее
auto result = std::async(std::launch::async, []() { return 42; });
result.get(); // Автоматически пробросит исключение
Вывод
В многопоточной программе исключения требуют явной обработки. Используйте std::async + future для большинства случаев — это безопасно и удобно. Для более сложных сценариев применяйте exception_ptr и promise.