← Назад к вопросам

Как можно проверить несколько потоков на наличие исключений?

2.0 Middle🔥 81 комментариев
#Многопоточность и синхронизация#Исключения и обработка ошибок

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Обработка исключений в многопоточной программе

Это критически важный аспект: исключение, выброшенное в одном потоке, НЕ автоматически попадает в основной поток. Нужна специальная обработка.

Проблема

#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.

Как можно проверить несколько потоков на наличие исключений? | PrepBro