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;
}
}
}