Какие знаешь способы создания потока в стандартной библиотеке?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Способы создания потока в стандартной библиотеке C++
C++11 введён стандартный механизм многопоточности через std::thread. Существует несколько способов создания потоков с использованием функций, функторов, лямбда-выражений и методов классов.
1. std::thread с функцией
Создание потока с обычной функцией
#include <thread>
#include <iostream>
void worker_function(int id) {
std::cout << "Worker " << id << " is running" << std::endl;
}
int main() {
std::thread t1(worker_function, 1); // Создаём поток
std::thread t2(worker_function, 2);
t1.join(); // Ждём завершения потока
t2.join();
return 0;
}
// Выведет: Worker 1 is running
// Worker 2 is running
Важно: Параметры передаются по значению по умолчанию. Для передачи по ссылке используй std::ref():
void modify(int& x) {
x = 42;
}
int main() {
int value = 10;
// Неправильно: изменение не повлияет на value
std::thread t1(modify, value);
t1.join();
std::cout << value; // Выведет 10, не 42
// Правильно: используем std::ref
std::thread t2(modify, std::ref(value));
t2.join();
std::cout << value; // Выведет 42
}
2. std::thread с функтором (callable object)
Класс с operator()
class Worker {
public:
void operator()(int id) const {
std::cout << "Worker " << id << " from functor" << std::endl;
}
};
int main() {
Worker w;
std::thread t(w, 1); // Копирует w в поток
t.join();
// Или с временным объектом
std::thread t2(Worker(), 2);
t2.join();
}
Осторожно с copy constructor
Фунтор копируется в поток, поэтому состояние не разделяется:
class Counter {
public:
void increment() { count++; }
int get_count() const { return count; }
private:
int count = 0;
};
int main() {
Counter c;
std::thread t1(c);
t1.join();
std::cout << c.get_count(); // Выведет 0, не 1!
// Копия c в потоке изменилась, но оригинал остался нетронутым
}
3. std::thread с лямбда-выражением
Простая лямбда
#include <thread>
int main() {
int id = 1;
std::thread t([id]() {
std::cout << "Worker " << id << " from lambda" << std::endl;
});
t.join();
}
Захват переменных
int main() {
int value = 10;
std::string name = "Alice";
// [id] — захват по значению
std::thread t1([value, name]() {
std::cout << name << ": " << value << std::endl;
// value и name скопированы в лямбду
});
// [&value, name] — смешанный захват
std::thread t2([&value, name]() {
value = 42; // Изменит оригинал
std::cout << value << std::endl;
});
// [&] — захват всех по ссылке (опасно!)
std::thread t3([&]() {
// Все переменные по ссылке
});
t1.join();
t2.join();
t3.join();
}
Осторожно с ссылками
void create_threads() {
std::vector<std::thread> threads;
int counter = 0;
// ОПАСНО: ссылка на локальную переменную
for (int i = 0; i < 3; i++) {
threads.emplace_back([&counter]() { // [&counter] опасна!
counter++;
});
}
for (auto& t : threads) t.join();
// counter может быть удалена до завершения потока
}
Правильно:
void create_threads() {
std::vector<std::thread> threads;
auto counter = std::make_shared<int>(0);
for (int i = 0; i < 3; i++) {
threads.emplace_back([counter]() { // Копируем shared_ptr
(*counter)++;
});
}
for (auto& t : threads) t.join();
std::cout << *counter; // Гарантированно 3
}
4. std::thread с методом класса
Вызов метода объекта
class MyClass {
public:
void member_function(int id) {
std::cout << "Method " << id << " called" << std::endl;
}
static void static_method(int id) {
std::cout << "Static " << id << " called" << std::endl;
}
};
int main() {
MyClass obj;
// Вызов метода члена: нужен указатель на объект
std::thread t1(&MyClass::member_function, &obj, 1);
t1.join();
// Вызов статического метода (как обычная функция)
std::thread t2(MyClass::static_method, 2);
t2.join();
}
С передачей объекта
int main() {
MyClass obj;
// Копируем объект в поток
std::thread t([obj]() {
// obj — копия, изменения не повлияют на оригинал
});
// Или передаём по ссылке (опасно!)
std::thread t2([&obj]() {
obj.member_function(1);
});
t.join();
t2.join();
}
5. std::thread с std::bind
#include <functional>
void work(int id, const std::string& name) {
std::cout << "Worker " << id << ": " << name << std::endl;
}
int main() {
// Привязываем параметры
auto task = std::bind(work, 1, "Alice");
std::thread t(task);
t.join();
// Или прямо в конструкторе
std::thread t2(std::bind(work, 2, "Bob"));
t2.join();
}
6. Управление потоком
RAII обёртка для безопасности
class ThreadGuard {
public:
explicit ThreadGuard(std::thread t) : thread_(std::move(t)) {}
~ThreadGuard() {
if (thread_.joinable()) {
thread_.join(); // Гарантированно присоединится
}
}
ThreadGuard(const ThreadGuard&) = delete;
ThreadGuard& operator=(const ThreadGuard&) = delete;
private:
std::thread thread_;
};
int main() {
{
ThreadGuard guard(std::thread([]() {
std::cout << "Thread is running" << std::endl;
}));
// При выходе из scope автоматически вызовется join()
}
}
Важные операции
std::thread t([]() { /* ... */ });
// Получить ID потока
std::thread::id id = t.get_id();
// Проверить, можно ли присоединиться
if (t.joinable()) {
t.join(); // Ждём завершения
}
// Или отделить поток (запустить в фоне)
t.detach(); // Поток работает независимо, не ждём
// Количество логических ядер
size_t cores = std::thread::hardware_concurrency();
std::cout << "CPU cores: " << cores << std::endl;
7. Пулы потоков и асинхронность
Для production лучше использовать std::async
#include <future>
int expensive_computation(int x) {
// Долгая операция
return x * x;
}
int main() {
// Запустить асинхронно
std::future<int> result = std::async(std::launch::async,
expensive_computation, 42);
// Делаем другую работу
std::cout << "Main thread working..." << std::endl;
// Получить результат (блокирует, если не готов)
int value = result.get();
std::cout << "Result: " << value << std::endl;
}
Или std::thread_pool (C++17+ или boost)
Рекомендации для production
- Используй std::thread для явного управления потоками
- Используй std::async для простых асинхронных операций
- Всегда используй RAII (ThreadGuard или какой-то пул)
- Избегай detach() — трудно отследить жизненный цикл
- Лямбды с захватом по значению — безопаснее чем по ссылке
- hardware_concurrency() для выбора числа потоков
- В production используй thread pool, а не создавай потоки вручную
Выбор способа зависит от контекста, но лямбда-выражение — наиболее современный и удобный подход для C++11 и новее.