concurrency - концепция выполнения двух или более задач одновременно, может быть поочерёдно parallelism - частный случай concurrency, физическое выполнение нескольких действий одновременно

В C++ многопоточность (std::thread) — это способ реализовать concurrency, позволяя программе управлять несколькими потоками, которые могут перекрываться по времени, если ядро одно. Если несколько ядер, то будет paralellism

поток исполнения - это передача управления внутри программы исполнения программы состоит из исполнения всех её потоков

с C++11 можно создавать потоки конструктор класса std::thread принимает функцию или функтор после завершения дел потоком нужно сделать .join() или .detach(), иначе UB .join() значит, что в этой точке мы ожидаем завершения потока .detach() значит, что нам всё равно, когда он остановится, мы его забываем

std::this_thread::get_id() - получить id текущего потока

гонка данных: если программа содержит как минимум 2 конфликтующих действия, одно из которых модифицирует область памяти, а другое читает или модифицирует ту же область и одно из них не атомарное и они не последовательные гонка данных это UB volatile не спасёт от гонки данных, тк UB есть UB

область памяти - это скалярный объект

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

по стандарту std::cout потокобезопасен

потоки шарят между собой ресурсы (память, файловые дескрипторы) и у них общее виртуальное адресное пространство каждый поток имеет отдельный сегмент для стека

std::mutex - позволяет защитить часть данных от одновременного обращения из разных потоков. вводит последовательность между доступами у std::mutex 3 метода:

  • lock() - если вызвать повторно в одном и том же потоке - UB, мб эксепшн system error
  • try_lock() - попробовать залочить, но если не получилось пойти дальше. если мьютекс кет-то захвачен, то вернёт false. если он захвачен нами же - UB
  • unlock() - если вызвать у незалоченного мьютекса - UB проблема - нельзя никак определить текущее состояние мьютекса

lock_guard<T> - RAII обёртка над любым T с интерфейсом .lock(), . unlock() not copyable, not movable пофакту const unique_ptr с кастомным deleter’ом, который делает unlock() если забыть дать имя lock_guard, то он умрёт в конце выражения lock_guard<mutex>{mutex};

от lock() до unlock() или от создания lock_guard до его деструктора - критическая секция, там, где может одновременно ходить один поток

гарантии безопасности относительно потоков:

  • нулевой уровень: нейтральные объекты, например int. безопасен, если мы его защитили синхронизацией потоков, не безопасен, если нет
  • хуже нейтрального: защита не даст синхронизацию. пример: указатели. если мы навернём мьютекс на указатель то, то что под указателем не будет защищено
  • лучше нейтрального: с этим объектом не может возникнуть data race

куча - глобальный синхронизированный ресурс. внутри new есть мьютекс

<threads>

void parallel(std::vector<int>& values) {
    size_t count = values.size();
    size_t threads_count = 100;
    size_t per_thread = count/threads_count;
    std::vector<std::thread> threads;
    for (int i = 0; i < threads_count; ++i) {
        auto begin = values.begin() + i * per_thread;
        auto end = values.begin() + (i + 1) * per_thread;
        threads.emplace_back(
            [&begin, &end] {
                std::generate(begin, end, []{ return rand() % 2; });
            }
        );
    }
    for (auto& thr : threads) thr.join();
}

OpenMP - открытый стандарт для реализации распараллеливания программ на C++, C и Fortran <omh.h>

void parallel(std::vector<int>& values) {
    #pragma omp parallel
    {
        #pragma omp for
        for (int i = 0; i < values.size(); ++i) {
            values[i] = rand() % 2;
        }
    }
}