Современные процессоры имеют несколько ядер, что позволяет выполнять несколько задач одновременно. Чтобы эффективно использовать вычислительные ресурсы, C++ предоставляет инструменты многопоточности через стандартную библиотеку <thread>, <future> и <mutex>. В этой статье мы рассмотрим ключевые элементы многопоточности в C++: std::thread, std::async и std::mutex.
Что такое многопоточность?
Многопоточность — это способность программы выполнять несколько потоков выполнения одновременно. Каждый поток — это легковесный процесс, который работает независимо, но может разделять память с другими потоками.
std::thread — запуск потоков вручную
Класс std::thread позволяет запускать новый поток, передав ему функцию или лямбда-выражение:
Пример:
#include <iostream>
#include <thread>
void sayHello() {
std::cout << "Привет из другого потока!" << std::endl;
}
int main() {
std::thread t(sayHello); // Запуск потока
t.join(); // Ожидание завершения потока
std::cout << "Главный поток завершён." << std::endl;
return 0;
}
t.join()— блокирует основной поток, покаtне завершится.- Вместо
join()можно использоватьdetach()— поток продолжит выполнение независимо, но вы потеряете к нему доступ.
std::async — асинхронные задачи с future
std::async — удобный способ выполнить функцию в другом потоке и получить результат через std::future.
Пример:
#include <iostream>
#include <future>
int computeSquare(int x) {
return x * x;
}
int main() {
std::future<int> result = std::async(computeSquare, 5);
std::cout << "Результат: " << result.get() << std::endl;
return 0;
}
std::future::get()— блокирует поток до завершения задачи.std::asyncможет сам выбрать, запускать задачу в новом потоке или отложить выполнение — для принудительного запуска используйте флагstd::launch::async.
std::future<int> result = std::async(std::launch::async, computeSquare, 5);
std::mutex — защита общей памяти
Когда несколько потоков обращаются к одной и той же переменной, нужно синхронизировать доступ. Для этого используется std::mutex.
Пример:
#include <iostream>
#include <thread>
#include <mutex>
int counter = 0;
std::mutex mtx;
void increment() {
for (int i = 0; i < 1000; ++i) {
std::lock_guard<std::mutex> lock(mtx);
++counter;
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Итоговое значение: " << counter << std::endl;
return 0;
}
std::lock_guardавтоматически блокирует мьютекс при создании и разблокирует при выходе из области видимости.- Альтернатива:
std::unique_lock— более гибкий, позволяет вручную блокировать/разблокировать мьютекс.
Краткие советы по многопоточности
- Избегайте гонок данных — всегда защищайте общие ресурсы с помощью
mutex. - Используйте
std::async, если нужно просто выполнить задачу и получить результат — это безопаснее, чем ручное управление потоками. - Не забывайте про
join()илиdetach()для всехstd::thread— иначе поток завершится аварийно. - По возможности применяйте RAII-обёртки (
lock_guard,unique_lock) — они предотвращают утечки и ошибки.
Заключение
Многопоточность в C++ — мощный инструмент, но требующий аккуратного обращения. С помощью std::thread, std::async и std::mutex вы можете эффективно распараллеливать задачи, управлять потоками и обеспечивать безопасный доступ к данным.
