Boost.Thread для C++: полное руководство с примерами
1. Что это и зачем нужна
Boost.Thread — это мощная и зрелая библиотека из состава Boost C++ Libraries, предоставляющая переносимые (кросс-платформенные) средства для работы с многопоточностью. Она появилась задолго до того, как стандарт C++11 ввел собственный модуль <thread>, и до сих пор остается востребованной благодаря своей гибкости, богатому набору инструментов и поддержке устаревших компиляторов. Библиотека позволяет создавать и управлять потоками, синхронизировать доступ к разделяемым данным, использовать блокировки, условные переменные, барьеры, семафоры, пулы потоков и многое другое.
Зачем нужна Boost.Thread, если есть стандартная библиотека? Во-первых, Boost.Thread предоставляет дополнительные примитивы, которых нет в стандарте C++ (например, barrier, future с поддержкой when_any, thread_group). Во-вторых, она имеет более богатый API для управления потоками (например, возможность задать стек потока, приоритет, имя). В-третьих, Boost.Thread гарантирует переносимость кода между различными платформами (Windows, Linux, macOS) и компиляторами (MSVC, GCC, Clang), что критично для крупных проектов. Наконец, Boost.Thread активно используется в legacy-проектах, где миграция на C++11/17/20 затруднена.
На образовательном сайте PythonLib мы рассматриваем Boost.Thread как отличный пример того, как выглядит "промышленная" библиотека для многопоточности в C++. Она учит правильной работе с разделяемыми ресурсами, помогает избежать deadlock'ов и race conditions, а также демонстрирует паттерны проектирования многопоточных приложений.
2. Установка
Boost.Thread не устанавливается через pip, npm или cargo — это библиотека для C++, которая устанавливается вместе с Boost. Ниже приведены точные команды для разных систем.
Linux (Ubuntu/Debian)
sudo apt-get update
sudo apt-get install libboost-all-dev
macOS (Homebrew)
brew install boost
Windows (vcpkg)
vcpkg install boost-thread
Windows (ручная сборка)
Скачайте Boost с официального сайта, распакуйте и выполните:
bootstrap.bat
.\b2 --with-thread
Сборка с CMake (пример)
В вашем CMakeLists.txt:
find_package(Boost REQUIRED COMPONENTS thread)
target_link_libraries(your_target PRIVATE Boost::thread)
3. Быстрый старт — минимальный рабочий пример
Создайте файл main.cpp:
#include <iostream>
#include <boost/thread.hpp>
void hello() {
std::cout << "Hello from thread! Thread ID: "
<< boost::this_thread::get_id() << std::endl;
}
int main() {
boost::thread t(hello);
t.join(); // Ждем завершения потока
std::cout << "Main thread finished." << std::endl;
return 0;
}
Скомпилируйте и запустите:
g++ main.cpp -lboost_thread -lboost_chrono -pthread -o main
./main
Вывод (ID потока будет другим):
Hello from thread! Thread ID: 0x7f8b9c0b9700
Main thread finished.
4. Основные методы/функции/классы
4.1. boost::thread — создание и управление потоками
Сигнатура:
explicit thread(const boost::function0<void>& thread_function);
void join();
void detach();
bool joinable() const;
Что делает: Класс boost::thread представляет собой отдельный поток выполнения. Конструктор принимает вызываемый объект (функцию, лямбду, функтор). join() блокирует вызывающий поток до завершения данного потока. detach() позволяет потоку выполняться независимо. joinable() проверяет, можно ли присоединить поток.
Пример:
#include <iostream>
#include <boost/thread.hpp>
void worker(int id) {
std::cout << "Worker " << id << " started." << std::endl;
boost::this_thread::sleep_for(boost::chrono::seconds(1));
std::cout << "Worker " << id << " finished." << std::endl;
}
int main() {
boost::thread t1(worker, 1);
boost::thread t2(worker, 2);
t1.join();
t2.join();
std::cout << "All workers done." << std::endl;
return 0;
}
4.2. boost::mutex — взаимное исключение
Сигнатура:
class mutex {
public:
void lock();
bool try_lock();
void unlock();
};
Что делает: Мьютекс (mutex) — базовый примитив синхронизации, который гарантирует, что только один поток может владеть блокировкой в любой момент времени. lock() блокирует поток до получения блокировки, try_lock() пытается получить блокировку без блокировки (возвращает true/false), unlock() освобождает блокировку.
Пример:
#include <iostream>
#include <boost/thread.hpp>
boost::mutex mtx;
int counter = 0;
void increment() {
for (int i = 0; i < 100000; ++i) {
mtx.lock();
++counter;
mtx.unlock();
}
}
int main() {
boost::thread t1(increment);
boost::thread t2(increment);
t1.join();
t2.join();
std::cout << "Counter: " << counter << std::endl; // Всегда 200000
return 0;
}
4.3. boost::lock_guard — RAII-блокировка
Сигнатура:
template<typename Mutex>
class lock_guard {
public:
explicit lock_guard(Mutex& m);
~lock_guard();
};
Что делает: Шаблонный класс, реализующий идиому RAII (Resource Acquisition Is Initialization). Захватывает мьютекс в конструкторе и автоматически освобождает его в деструкторе. Это гарантирует, что блокировка будет снята даже при возникновении исключения. Предпочтительнее ручного вызова lock()/unlock().
Пример:
#include <iostream>
#include <boost/thread.hpp>
boost::mutex mtx;
void safe_print(const std::string& msg) {
boost::lock_guard<boost::mutex> guard(mtx);
std::cout << msg << std::endl;
} // guard автоматически разблокирует mtx здесь
int main() {
boost::thread t1([](){ safe_print("Hello from t1"); });
boost::thread t2([](){ safe_print("Hello from t2"); });
t1.join();
t2.join();
return 0;
}
4.4. boost::unique_lock — гибкая блокировка
Сигнатура:
template<typename Mutex>
class unique_lock {
public:
explicit unique_lock(Mutex& m);
void lock();
bool try_lock();
void unlock();
Mutex* mutex() const;
bool owns_lock() const;
};
Что делает: Более гибкая версия lock_guard. Позволяет отложить блокировку, попытаться заблокировать без ожидания, явно разблокировать и снова заблокировать. Используется совместно с условными переменными.
Пример:
#include <iostream>
#include <boost/thread.hpp>
boost::mutex mtx;
int data = 0;
bool ready = false;
void producer() {
boost::this_thread::sleep_for(boost::chrono::seconds(1));
boost::unique_lock<boost::mutex> lock(mtx);
data = 42;
ready = true;
lock.unlock(); // Явно разблокируем
std::cout << "Produced data." << std::endl;
}
void consumer() {
boost::unique_lock<boost::mutex> lock(mtx);
while (!ready) {
lock.unlock();
boost::this_thread::yield(); // Уступаем процессор
lock.lock();
}
std::cout << "Consumed data: " << data << std::endl;
}
int main() {
boost::thread t1(producer);
boost::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
4.5. boost::condition_variable — условная переменная
Сигнатура:
class condition_variable {
public:
void wait(unique_lock<mutex>& lock);
template<typename Predicate>
void wait(unique_lock<mutex>& lock, Predicate pred);
void notify_one();
void notify_all();
};
Что делает: Условная переменная позволяет потокам ожидать наступления определенного условия. wait() атомарно разблокирует мьютекс и блокирует поток до получения уведомления. Перегруженная версия с предикатом защищает от ложных пробуждений (spurious wakeups). notify_one() пробуждает один ожидающий поток, notify_all() — все.
Пример (правильный producer-consumer):
#include <iostream>
#include <queue>
#include <boost/thread.hpp>
boost::mutex mtx;
boost::condition_variable cv;
std::queue<int> buffer;
const int MAX_SIZE = 5;
void producer() {
for (int i = 0; i < 10; ++i) {
boost::unique_lock<boost::mutex> lock(mtx);
cv.wait(lock, []{ return buffer.size() < MAX_SIZE; });
buffer.push(i);
std::cout << "Produced: " << i << std::endl;
cv.notify_all();
}
}
void consumer() {
for (int i = 0; i < 10; ++i) {
boost::unique_lock<boost::mutex> lock(mtx);
cv.wait(lock, []{ return !buffer.empty(); });
int val = buffer.front();
buffer.pop();
std::cout << "Consumed: " << val << std::endl;
cv.notify_all();
}
}
int main() {
boost::thread t1(producer);
boost::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
4.6. boost::barrier — барьер синхронизации
Сигнатура:
class barrier {
public:
explicit barrier(unsigned int count);
bool wait();
};
Что делает: Барьер блокирует потоки до тех пор, пока заданное количество потоков (count) не вызовет wait(). После этого все потоки разблокируются одновременно. Возвращаемое значение wait() — true только для одного из потоков (обычно последнего), что можно использовать для выполнения одноразовых действий.
Пример:
#include <iostream>
#include <boost/thread.hpp>
boost::barrier bar(3); // Ждем 3 потока
void worker(int id) {
std::cout << "Worker " << id << " waiting at barrier." << std::endl;
bool is_last = bar.wait();
if (is_last) {
std::cout << "All workers passed the barrier!" << std::endl;
}
std::cout << "Worker " << id << " continues." << std::endl;
}
int main() {
boost::thread t1(worker, 1);
boost::thread t2(worker, 2);
boost::thread t3(worker, 3);
t1.join();
t2.join();
t3.join();
return 0;
}
4.7. boost::shared_mutex — мьютекс с разделяемым доступом
Сигнатура:
class shared_mutex {
public:
void lock();
void unlock();
void lock_shared();
void unlock_shared();
};
Что делает: Позволяет множеству потоков одновременно читать данные (разделяемая блокировка, lock_shared), но только одному потоку — писать (эксклюзивная блокировка, lock). Идеально подходит для сценариев "читатели-писатели". Используется с boost::shared_lock для RAII-блокировки