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

Какие знаешь способы создания потока в стандартной библиотеке?

2.2 Middle🔥 111 комментариев
#Многопоточность и синхронизация#Язык C++

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

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

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

Способы создания потока в стандартной библиотеке 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

  1. Используй std::thread для явного управления потоками
  2. Используй std::async для простых асинхронных операций
  3. Всегда используй RAII (ThreadGuard или какой-то пул)
  4. Избегай detach() — трудно отследить жизненный цикл
  5. Лямбды с захватом по значению — безопаснее чем по ссылке
  6. hardware_concurrency() для выбора числа потоков
  7. В production используй thread pool, а не создавай потоки вручную

Выбор способа зависит от контекста, но лямбда-выражение — наиболее современный и удобный подход для C++11 и новее.

Какие знаешь способы создания потока в стандартной библиотеке? | PrepBro