Compare-And-Swap не работает на многих ядрах

Когда я обнаружил инструкцию «CAS», я помню, что хорошо понимал, что она может работать для потоков, работающих на одном процессоре, но я был удивлен, что она может работать для многих процессоров.

Вчера у меня появилась первая возможность протестировать его на одной из своих разработок. Я реализовал это, и это действительно сработало; все мои модульные тесты были зелеными. Идеально.

Но сегодня я запустил свои модульные тесты на другой машине, и теперь они терпят неудачу. Менее совершенный

Основное различие между двумя машинами заключается в том, что первая (та, на которой юнит-тесты зеленые) — это довольно старый ноутбук с одним ядром! Второй - более свежий i7 и более мощный...

Теперь, на моем i7, если я заставлю свои модульные тесты работать на одном ядре, они станут успешными. Я делаю это, запустив

taskset -c <cpu-id> my-unit-test

Законно, мой первоначальный вопрос возвращается: работает ли CAS на многих ядрах? Хорошо, судя по тому, что я читал, я был бы удивлен, если бы это было не так...

И что? Я надеюсь, что это происходит из-за ошибки в моем коде. Чтобы дать вам больше информации, у меня есть класс с критической секцией. я добавил атрибут

bool m_isBeingModified;

Он инициализируется как false. Более того, в начале моей критической секции я запускаю функцию

inline void waitForClassBeingModified()
{
  while (!__sync_bool_compare_and_swap(&m_isBeingModified, false, true))
  {} /// I concider that I can to such a loop as my critical section is very light/short
}

Наконец, в конце моего критического раздела я сбрасываю свою логическую переменную

 m_isBeingModified = false;

Я попытался установить свой атрибут как volatile, но это ничего не изменило: мои модульные тесты все еще не работают.

Последняя информация:

gcc --version
gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Спасибо за помощь


person Philippe MESMEUR    schedule 30.03.2017    source источник
comment
Вы запускаете свой собственный мьютекс в качестве учебного опыта или пишете производственный код? В последнем случае вам может потребоваться пересмотреть использование объектов синхронизации, предоставляемых библиотеками (например, pthread) или операционной системой.   -  person Michael Burr    schedule 30.03.2017
comment
Мой код не для производства. Это скорее тест для понимания использования CAS через __sync_bool_compare_and_swap.   -  person Philippe MESMEUR    schedule 30.03.2017


Ответы (2)


Также используйте __sync_bool_compare_and_swap для сброса переменной вместо m_isBeingModified = false;. Кроме того, не реализуйте свой собственный мьютекс...

И компилятор, и ЦП могут изменить порядок кода непреднамеренным образом. Примитивы __sync помечены таким образом, чтобы предотвратить такое переупорядочение. Таким образом, с m_isBeingModified = false; вполне может случиться так, что компилятор сначала установит переменную в false и только затем сгенерирует код для того, что вы намеревались поместить внутри критической области.

person Uli Schlachter    schedule 30.03.2017
comment
Спасибо. Я очень удивлен переупорядочением, поскольку оно также происходит, если я объявляю свой атрибут как volatile. Однако, кажется, вы правы: если я также использую __sync_bool_compare_and_swap для сброса моей переменной, моя проблема исчезает. - person Philippe MESMEUR; 31.03.2017
comment
Что вы имеете в виду под «Также не реализовывать свой собственный мьютекс»? Вы имеете в виду, что я должен использовать стандартные мьютексы pthread, или вы имеете в виду что-то другое? - person Philippe MESMEUR; 31.03.2017
comment
Да, я имею в виду, что вы должны просто использовать стандартные мьютексы pthread. - person Uli Schlachter; 31.03.2017
comment
volatile имеет значение только для компилятора. ЦП его вообще не видит, так что по-прежнему разрешено переупорядочивать доступ к памяти вокруг этого хранилища. Кроме того, volatile не имеет большого значения в стандарте C. Я действительно не знаю о ситуации, когда это помогает. - person Uli Schlachter; 31.03.2017

Благодаря драгоценной помощи Ули, я думаю, что теперь у меня есть все элементы, чтобы ответить на мой вопрос.

Во-первых, я, возможно, не совсем понял, но функция, которую я хочу защитить от одновременного доступа, очень легкая. Для завершения требуется около 80 циклов процессора (TSC). Вот почему я предпочитаю реализовать свой собственный «легкий» параллельный мьютекс на основе одного CAS, чем использовать pthread_mutex.

Я нашел эту интересную страницу, на которой объясняется, как "временно" отключить код- повторный заказ благодаря следующей инструкции:

__asm__ __volatile__("":::"memory");

Используя его, я действительно повышаю свою защиту от параллелизма и, конечно же, все мои тесты по-прежнему успешны.

Чтобы получить сводку, в следующем списке сообщается о влиянии на производительность различных решений, которые я пробовал:

  • Исходный код (без защиты): около 80 TSC.
  • Двойной CAS (установленная и неустановленная переменная): около 105 TSC.
  • Решение на основе мьютексов: около 120 TSC
  • Один CAS + отключение переупорядочивания: около 85 TSC
person Philippe MESMEUR    schedule 01.04.2017