Именно для безопасного манипулирования данными в параллельной среде QNX API и вводятся атомарные операции. Десять атомарных функций делятся на две симметричные группы по виду своего именования и логике функционирования. Все атомарные операции осуществляются только над одним типом данных
unsigned int
, но, как будет показано далее, это не такое уж и сильное ограничение. Сам объект, над которым осуществляется атомарная операция (типа
unsigned int
), — это самая обычная переменная целочисленного типа, только описанная с квалификатором
volatile
.
Помимо
атомарных операций над этой переменной могут выполняться любые другие действия, которые можно считать безопасными в многопоточной среде: инициализация, присваивание значений, сравнения. Более того, при выходе программы за область возможного многопоточного доступа к этой переменной она может далее использоваться любым традиционным и привычным образом.
Важно также отметить, что термин «атомарность» относится не к особым свойствам некоторого объекта данных, а к ограниченному ряду операций, которые можно безопасно выполнять над этим объектом в многопоточной среде.
Общий вид прототипов каждой из двух групп атомарных операций следующий:
должно стоять имя одной из пяти операций (таким алгоритмом и обеспечивается 10 различных атомарных функций):
add
— добавить численное значение к операнду;
sub
— вычесть численное значение из операнда;
clr
— очистить битыв значении операнда (выполняется побитовая операция (
*D) &= ~S
);
set
— установить битыв значении операнда (выполняется побитовая операция (
*D) |= S
);
toggle
— инвертировать битыв значении операнда (выполняется побитовая операция (
*D) ^= S
);
D
— именно тот объект, над которым осуществляется атомарная операция;
S
— второй операнд осуществляемой операции.
Две формы атомарных функций для каждой операции отличаются тем, что первая из них выполняет операцию без возврата значения, а вторая возвращает значение, которое операнд
D
имел до выполнения операции (т.e. прежнее значение, как это делают, например, префиксные операции инкремента
++D
и декремента
– -D
, в отличие от постфиксных
D++
и
D--
).
Зачем нужны две формы для операции? Техническая документация QNX утверждает, что вторая форма может выполняться дольше. Справедливость этого утверждения и насколько дольше выполняется вторая форма, мы скоро увидим на примерах.
Итак, у нас есть 10 функций для выполнения пяти атомарных операций:
atomic_add atomic_add_value
atomic_sub atomic_sub_value
atomic_clr atomic_clr_value
atomic_set atomic_set_value
atomic_toggle atomic_toggle_value
Как используются атомарные операции? Обычно для предотвращения одновременного изменения некоторого счетчика индекса мы вынуждены создавать критическую секцию, обозначая ее, скажем, операциями над мьютексом. В частности, в следующем примере нам необходимо из различных потоков последовательно дописывать некоторые байтовые результаты в единый буфер: