Семинар 23. Синхронизация потоков
Архитектура компьютера и операционные системы @ ВШЭ, 2018-2019

Data race
- Если несколько потоков одновременно обращаются к одной и той же ячейке памяти, причем хотя бы один из потоков выполняет запись в эту ячейку, такая ситуация называется
data race
- В случае, когда два потока одновременно работают со сложным объектом, а не с примитивным типом, необходимо понять, является ли объект потокобезопасным
- Как правило, если методы не модифицируют объект, то их можно вызывать параллельно, а если модифицируют - то нельзя
Атомарность работы с памятью
- На всех современных процессорах операции чтения из памяти и записи в память натурально-выравненных значений размера не более машинного слова атомарны
- Это означает, что в случае одновременного чтения одним ядром и записи другим ядром значения по одному и тому же адресу, значение не "рассыпется на куски" частично старого, а частично нового значения
- Такая семантика чтения и записи явно и неявно используется в большом объеме существующего программного кода
Атомарность работы с памятью
- Строго говоря, с точки зрения стандартов Си и Си++ предположение об атомарности памяти некорректно
- Более того, даже примитивные операции (
++i;
) не атомарны даже при атомарности памяти - Ключевое слово
volatile
не дает никакой гарантии атомарности, использовать его для этого нельзя - Для атомарных операций в
C
и C++
необходимо использовать специальные контрукции
Atomic
- Параллельная работа с атомарными переменными свободна от гонок
- Может быть
lock-free
с помощью специальных инструкций процессора, либо используется мьютекс
Atomic
- В
C++
используется std::atomic
. Гарантированно lock-free
для bool
, на практике - примитивные типы тоже - В
C
- ключевое слово _Atomic
, заголовочный файл stdatomic.h
, atomic_bool
, atomic_int
, ... - Поддерживаются основные операции (
=
, +=
, ...) - Подробнее, подробнее
Atomic
- Lock-free atomic-примитивы используются для построения lock-free структур данных. В таких структурах данных изменения для внешнего пользователя происходят атомарно
- Обычно в lock-free структурах данных сначала подготавливаются изменения, а затем атомарно применяются
- Пример - вставка в односвязный список
Mutex
- Примитив синхронизации, который может быть заблокирован или разблокирован
- Если мьютекс заблокирован, то следующая попытка блокировки будет ожидать предварительной разблокировки
- Позволяет выделять "критические секции" - участки кода, для которых важно, чтобы только один поток мог одноврененно в них находиться
pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&counter_mutex);
pthread_mutex_unlock(&counter_mutex);
Condition variable
- Мы хотим иметь возможность поместить поток в состояние сна (блокировки), а когда наступит какое-то нужное нам событие, разбудить один или все потоки, которые спят в ожидании этого события
pthread_cond_wait
- ожидание получение сигналаpthread_cond_signal
- послать сигнал одному из ожидающих потоковpthread_cond_broadcast
- послать сигнал всем ожидающим потокам- Используется мьютекс для избежания гонок