#c #gcc #locking #atomic #inline-assembly
#c #gcc #блокировка #атомарный #встроенная сборка
Вопрос:
У меня есть следующий фрагмент кода для сравнения и обмена
18 static size_t compareAndExchange( volatile size_t* addr, size_t oldval, size_t newval )
19 {
20 size_t ret;
21 __asm__ volatile( "lock cmpxchgq %2, %1nt"
22 :"=a"(ret), " m"(*addr)
23 : "r"(newval), "0"(oldval)
24 : "memory" );
25 return ret;
26 }
Ниже приведен тест для сравнения и обмена
4 struct node
5 {
6 struct node* next;
7 int locked;
8
9 node():next( 0 ), locked( false ) {}
10 };
11
12 int main()
13 {
14 struct node* head = 0;
15 struct node* temp = new struct node;
16
17 struct node* ret = reinterpret_cast< struct node* > (
18 AtomicUtils::compareAndExchange(
19 reinterpret_cast< volatile size_t*>( amp;head ),
20 0,
21 reinterpret_cast< size_t >( temp )
22 )
23 ) ;
24
25 std::cout<< "nExpected: Ret = " << 0 << "Found: " << 0 << std::endl;
26 std::cout<< "nExpected: head = " << temp << "Found: " << head << std::endl;
27 }
Вопросы:
1) В настоящее время, чтобы атомарно установить значение head, я обманываю, преобразуя указатель size_t ** в указатель size_t * с помощью reinterpret_cast . Это выглядит некрасиво. Есть ли лучший способ (возможно, с помощью лучшей реализации встроенной сборки) для достижения того же эффекта?
2) Зачем нам нужен квалификатор volatile для аргумента?
Комментарии:
1. Можете ли вы использовать
std::atomic
из C 11?2. В моей компании у нас нет версии gcc, которая поддерживает c 11
3. Этот код вообще работает? Похоже, вы никогда не устанавливаете накопитель…
4. @KerrekSB: cmpxcgh выдает «результат» в EAX.
5. @MatsPetersson: Он также ожидает «ввода» в EAX…
Ответ №1:
В C вы могли бы использовать шаблоны:
template<typename T>
static T compareAndExchange( volatile T* addr, T oldval, T newval )
{
.... rest of code should remain the same ...
}
И ваш тестовый код теперь будет:
node* ret = AtomicUtils::compareAndExchange(amp;head, 0, temp);
(Я так понимаю, вы знаете о std::atomic и его членах, и вы просто делаете это в качестве учебного упражнения [я вижу, по-видимому, другие причины не использовать это])
gcc также поддерживает встроенные атомарные функции (то есть функции, которые компилятор может напрямую транслировать в машинный код), одной из которых является __sync_bool_compare_and_swap
.
Смотрите больше здесь:https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html
Редактировать: вы бы заметили, если что-то пойдет не так, если вы измените это:
std::cout<< "nExpected: Ret = " << 0 << "Found: " << 0 << std::endl;
Для:
std::cout<< "nExpected: Ret = " << 0 << "Found: " << ret << std::endl;
Edit2: Что касается вашего второго вопроса, о volatile
: вам это не нужно для вашей функции подкачки как таковой [кроме самой __asm__
, поскольку это не позволяет компилятору оптимизировать ее и потенциально изменять порядок инструкций и т.д.]. Вам это действительно нужно, если вы когда-либо собираетесь использовать volatile T*
аргумент для самой функции swap, или вам нужно const_cast
удалить volatile
атрибут.
Комментарии:
1. Я использую clang. Итак, я не могу использовать атомарику gcc.
2. @KodeWarrior: я был почти уверен, что clang совместим с g в этом контексте, но если у вас достаточно новый clang, вы сможете использовать clang.llvm.org/docs/LanguageExtensions.html#langext-c11-atomic
3. Спасибо за указатель. Ценю это! В настоящее время я пытаюсь поиграть со встроенной сборкой в качестве учебного упражнения.
4. Я это понимаю. Просто он намного менее переносим (например, если вы скомпилируете с -m32, ваш код не будет работать. Встроенные средства позаботятся об этом (а также, например, поддерживают произвольные типы указателей).
5. да, то, что вы говорите, определенно имеет смысл. Спасибо.