Найти тему
CodeDream

Реализация "легкого" spinlock на атомарных операциях

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

Читая эту документацию и разбираясь с тем как все это работает, мне пришла в голову идея легкого spinlock'a на атомарных функциях. В чем же легкость этого spinlock'a относительного того же mutex'a?! Легкость заключается в том что эта блокировка не приводит к системному вызову, как например блокировка mutex'a.

Итак для создание легкого spinlock'a в userspace нам потребуются 2 функции:

  • bool __atomic_test_and_set (void *ptr, int memorder)
  • void __atomic_clear (bool *ptr, int memorder)

__atomic_test_and_set - устанавливает байт *ptr в некое предопределенное значение 'set'. Функция вернет true только в том случае если байт *ptr до этого уже находился в состояние 'set', иначе функция вернет false. Функцию стоит использовать только с типами char и bool, иначе *ptr будем модифицировано только частично.

__atomic_clear - функция сбрасывает байт *ptr в 0.

int memorder переменная обозначающая используемый способ упорядочения памяти. Это отдельная, довольно обширная тема барьеров памяти разбирать которую в рамках этой статьи я не буду, нам вполне значение по умолчанию __ATOMIC_SEQ_CST, самый строгий ordering. Хотя, если я правильно понял принципы memory ordering'a, то можно использовать более оптимальный Acquire/Release memory ordering.

Теперь сами функции:

void spinlock_lock(bool *lock)
{
/*
* Крутимся в цикле(spinlock же) пока функция не вернет false,
* что будет означать что мы захватили блокировку
*/
while(__atomic_test_and_set(lock, __ATOMIC_SEQ_CST) != false);
}

void spinlock_unlock(bool * lock)
{
__atomic_clear(lock, __ATOMIC_SEQ_CST);
}

Ничего сложного. Не требует подключения дополнительных заголовочных файлов.