Библиотека многопоточности C++<thread>

C++11 представил поддержку многопоточности, с помощью<thread>библиотеки разработчики могут легко реализовать параллельную обработку в программах.

В этой статье будут представлены<thread>основные концепции, определения, синтаксис библиотеки и способы её использования для создания потоков и управления ими.

Поток — это наименьшая единица выполнения программы и наименьшая единица, которую операционная система может планировать для вычислений.

В многопоточных программах несколько потоков могут выполняться параллельно, повышая эффективность выполнения программы.

C++ <thread>Обзор библиотеки

<thread>Библиотека является частью стандартной библиотеки C++, предоставляя базовую функциональность для создания потоков и управления ими. Она включает следующие ключевые компоненты:

  • std::thread: представляет поток; он может создавать, запускать, ожидать завершения и уничтожать потоки.
  • std::this_thread: предоставляет некоторые статические функции-члены для работы с текущим потоком.
  • std::thread::id: уникальный идентификатор потока.

Создание потоков

Чтобы создать поток, необходимо создать экземплярstd::threadкласса и передать вызываемый объект (функцию, лямбда-выражение или функцию-член объекта) в качестве аргумента.

Пример

#include <iostream>
#include <thread>

void print_id(int id) {
    std::cout << "ID: " << id << ", ID потока: " << std::this_thread::get_id() << std::endl;
}

int main() {
    std::thread t1(print_id, 1);
    std::thread t2(print_id, 2);
}

Запуск потоков

Создайтеstd::threadПосле создания объекта поток немедленно начнёт выполнение; можно вызватьjoin()метод, чтобы дождаться завершения потока.

t1.join();
t2.join();

Ожидание завершения потока

join()метод блокирует текущий поток до завершения выполнения вызванного потока.

Уничтожение потоков

Когда поток завершил выполнение, можно использоватьdetach()метод, чтобы отсоединить поток, или позволитьstd::threadобъекту выйти из области видимости и уничтожиться автоматически.

t1.detach(); // 线程将继续运行,但无法再被 join или  detach

Пример: использование<thread>Создание параллельных вычислений

Ниже приведён пример использования<thread>Пример параллельных вычислений, реализованных с помощью библиотеки, вычисляющий сумму двух чисел.

Пример

#include <iostream>
#include <thread>

int sum = 0;

void add(int a, int b) {
    sum += a + b;
}

int main() {
    int a = 5;
    int b = 10;

    std::thread t1(add, a, b);
    std::thread t2(add, a, b);

    t1.join();
    t2.join();

    std::cout << "Сумма: " << sum << std::endl; // Результат вывода: Sum: 30
}

Результат вывода:

Sum: 30

В следующем примере мы создадим два потока, каждый из которых выполняет простую функцию, которая выводит сообщение и спит некоторое время:

Пример

#include <iostream>
#include <thread>
#include <chrono>

// Простая функция, выполняемая в потоке
void print_message(const std::string& message, int delay) {
    std::this_thread::sleep_for(std::chrono::milliseconds(delay));
    std::cout << message << std::endl;
}

int main() {
    // Создайте два потока и выполните функцию print_message
    std::thread t1(print_message, "Hello from thread 1", 1000);
    std::thread t2(print_message, "Hello from thread 2", 500);

    // Дождаться завершения потока t1
    if (t1.joinable()) {
        t1.join();
    }

    // Дождаться завершения потока t2
    if (t2.joinable()) {
        t2.join();
    }

    std::cout << "Main thread finished." << std::endl;

    return 0;
}

Результат вывода:

Hello from thread 2
Hello from thread 1
Main thread finished.

Примечания

  • Потокобезопасность: В многопоточной среде общие ресурсы требуют синхронизированного доступа, чтобы избежать гонок данных.
  • Жизненный цикл потока: Убедитесь, что объект потока корректно обрабатывается после завершения выполнения потока, чтобы избежать утечек ресурсов.

Классы и функции

<thread>Библиотека содержит ряд классов и функций для создания, управления и синхронизации потоков.

Далее речь пойдёт о C++<thread>Подробное введение в библиотеку:

Основные компоненты

  • std::thread
  • std::mutex
  • std::lock_guard
  • std::unique_lock
  • std::condition_variable
  • std::future и std::promise
  • std::async

std::thread

Класс std::thread используется для создания и управления потоками

Пример

#include <iostream>
#include <thread>

void print_hello() {
    std::cout << "Привет из потока!" << std⟧::endl;
}

int main() {
    std::thread t(print_hello);
    t.join(); // Wait for thread t to finish
    return 0;
}

Важно

  • join(): Ожидать завершения потока.
  • detach(): Перевести поток в фоновый режим и больше не ожидать его завершения.
  • joinable(): Проверить, можно ли присоединить или отсоединить поток.

std::mutex

Класс std::mutex используется для синхронизации доступа к общим ресурсам.

Пример

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx; // Создать глобальный объект mutex
int shared_resource = 0; // Общий ресурс

// Функция потока
void increment() {
    std::lock_guard<std::mutex> lock(mtx); // Блокировка для обеспечения потокобезопасности
    ++shared_resource;
    std::cout << "Значение shared_resource увеличено до " << shared_resource << std::endl;
    // lock автоматически освобождается, когда lock_guard выходит из области видимости
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);
   
    t1.join(); // Ожидание завершения потока t1
    t2.join(); // Ожидание завершения потока t2

    std::cout << "Итоговое значение shared_resource: " << shared_resource << std::endl;

    return 0;
}

std::lock_guard

std::lock_guard — это RAII-менеджер блокировок, используемый для автоматического управления жизненным циклом блокировки.

Пример

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;

void print_thread_id(int id) {
    std::lock_guard<std::mutex> lock(mtx);
    std::cout << "Thread ID: " << id << std::endl;
}

int main() {
    std::thread t1(print_thread_id, 1);
    std::thread t2(print_thread_id, 2);
    t1.join();
    t2.join();
    return 0;
}

std::unique_lock

std::unique_lock обеспечивает более гибкое управление блокировкой, чем std::lock_guard.

Пример

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;

void print_thread_id(int id) {
    std::unique_lock<std::mutex> lock(mtx);
    std::cout << "Thread ID: " << id << std::endl;
    lock.unlock(); // Можно разблокировать вручную
    // ... другие операции
}

int main() {
    std::thread t1(print_thread_id, 1);
    std::thread t2(print_thread_id, 2);
    t1.join();
    t2.join();
    return 0;
}

std::condition_variable

std::condition_variable используется для ожидания и уведомления между потоками.

Пример

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void print_id(int id) {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return ready; });
    std::cout << "Thread ID: " << id << std::endl;
}

void set_ready() {
    std::unique_lock<std::mutex> lock(mtx);
    ready = true;
    cv.notify_all();
}

int main() {
    std::thread t1(print_id, 1);
    std::thread t2(print_id, 2);
   
    std::this_thread::sleep_for(std::chrono::seconds(1));
    set_ready();
   
    t1.join();
    t2.join();
    return 0;
}

std::future и std::promise

std::future и std::promise используются для передачи результатов между потоками.

Пример

#include <iostream>
#include <thread>
#include <future>

void calculate_square(std::promise<int> && p, int x) {
    p.set_value(x * x);
}

int main() {
    std::promise<int> p;
    std::future<int> f = p.get_future();
   
    std::thread t(calculate_square, std::move(p), 5);
   
    std::cout << "Square: " << f.get() << std::endl;
   
    t.join();
    return 0;
}

std::async

std::async используется для запуска асинхронных задач и возвращает std::future.

Пример

#include <iostream>
#include <future>

int calculate_square(int x) {
    return x * x;
}

int main() {
    std::future<int> result = std::async(calculate_square, 5);
   
    std::cout << "Square: " << result.get() << std::endl;
   
    return 0;
}

C++'s<thread>Библиотека предоставляет разработчикам мощную поддержку многопоточности. Благодаря введению в этой статье мы сможем понять основные концепции потоков и научиться использовать<thread>библиотеку для создания потоков и управления ими. В реальной разработке разумное использование многопоточности может значительно повысить производительность и отзывчивость программы.

Другие расширения