Что такое поток (thread) в программировании?

Ответ

Поток (thread) — это наименьшая единица выполнения, которую может планировать операционная система. В рамках одного процесса может существовать множество потоков, которые разделяют его виртуальное адресное пространство (глобальные переменные, куча, открытые файлы), но имеют собственные стеки вызовов и регистры процессора.

Зачем нужны потоки в C++?

  • Параллелизм: Использование нескольких ядер CPU для ускорения вычислений.
  • Отзывчивость: Выполнение длительных операций (например, I/O) в фоновом потоке, не блокируя основной поток пользовательского интерфейса.
  • Асинхронная обработка: Одновременное обслуживание множества сетевых подключений или запросов.

Базовый пример с std::thread (C++11 и выше):

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

void threadFunction(int id) {
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::cout << "Hello from thread #" << id << " (ID: "
              << std::this_thread::get_id() << ")" << std::endl;
}

int main() {
    std::cout << "Main thread ID: " << std::this_thread::get_id() << std::endl;

    std::thread t1(threadFunction, 1); // Создание и запуск потока
    std::thread t2(threadFunction, 2);

    // Основной поток ждёт завершения t1 и t2
    t1.join();
    t2.join();

    std::cout << "Both threads finished." << std::endl;
    return 0;
}

Критически важные моменты:

  1. Синхронизация: Так как потоки разделяют память, необходим контроль доступа к общим данным для предотвращения состояний гонки (race conditions). Для этого используются:
    • std::mutex, std::lock_guard, std::unique_lock
    • std::atomic типы
    • Условные переменные (std::condition_variable)
  2. Управление жизненным циклом: Поток должен быть либо присоединён (joined) с помощью join() (основной поток ждёт его завершения), либо отсоединён (detached) с помощью detach() (поток выполняется самостоятельно). Невыполнение этого требования до разрушения объекта std::thread приводит к вызову std::terminate.
  3. Проблемы: Взаимные блокировки (deadlocks), инверсия приоритетов, ложное разделение кэша (false sharing).

Ответ 18+ 🔞

Давай разжую тебе про потоки, а то смотрю, глаза стекленеют. Представь, что твоя программа — это один мужик на стройке. Он и цемент мешает, и кирпичи таскает, и закупается. Работает, конечно, но медленно, как черепаха в патрулях. А теперь представь, что у тебя целая бригада таких мужиков — это и есть потоки. Все они копаются в одной общей куче ресурсов (это память процесса), но у каждого свой личный инструмент (стек) и своя голова (регистры).

Ну и нахуя это надо?

  • Чтобы быстрее: Вместо того чтобы одним ядром процессора всё считать, можно загрузить все ядра по полной. Овердохуища задач — овердохуища потоков.
  • Чтобы не тупить: Твой интерфейс не виснет, когда программа что-то качает или считает. Долгая операция пошла в фоновый поток, а ты себе кликай дальше.
  • Чтобы всех обслуживать: Веб-сервер, который на каждый чих клиента создаёт отдельный поток — классика, ёпта.

Смотри, как просто это выглядит в коде (C++11 и новее):

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

void threadFunction(int id) {
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::cout << "Hello from thread #" << id << " (ID: "
              << std::this_thread::get_id() << ")" << std::endl;
}

int main() {
    std::cout << "Main thread ID: " << std::this_thread::get_id() << std::endl;

    std::thread t1(threadFunction, 1); // Создание и запуск потока
    std::thread t2(threadFunction, 2);

    // Основной поток ждёт завершения t1 и t2
    t1.join();
    t2.join();

    std::cout << "Both threads finished." << std::endl;
    return 0;
}

А теперь, чувак, лови подводные грабли, об которые все расшибаются:

  1. Синхронизация — это пиздец. Раз все лезут в одну кучу памяти, начинается бардак. Один поток пишет в переменную, другой в это же время читает — и ты получаешь состояние гонки (race condition), хуйню полную. Чтобы такого не было, нужны мьютексы (std::mutex), атомарные операции (std::atomic) и прочие замки. Без этого твоя многопоточка превратится в манда с ушами.
  2. Жизненный цикл. С потоком нужно определиться, как с котом: либо ты за ним приглядываешь до конца (join()), либо отпускаешь нахуй и забываешь (detach()). Если ты создал std::thread, а он ещё работает, и программа уже завершается — будет тебе std::terminate, всё накрылось медным тазом. Распиздяйство недопустимо.
  3. Проблемы. Тут тебе и взаимные блокировки (deadlock), когда два потока ждут друг друга, как идиоты, и ложное разделение кэша, из-за которого всё тормозит. В общем, волнение ебать, но разобраться можно, если голову включить.