Re[5]: volatile - нужен ли при pthread_mutex_lock() и в GTK?
От: ДимДимыч Украина http://klug.org.ua
Дата: 12.09.09 13:13
Оценка:
Здравствуйте, Zhendos, Вы писали:

Z>подробности можно найти здесь:

Z>http://lkml.org/lkml/2007/10/24/673

Мда, исправлять придурь компилятора прибиванием переменных гвоздями — очень хорошее решение
Обязательно бахнем! И не раз. Весь мир в труху! Но потом. (ДМБ)
Re[11]: оффтоп
От: ДимДимыч Украина http://klug.org.ua
Дата: 14.09.09 12:53
Оценка:
Здравствуйте, netch80, Вы писали:

N>какой "дефолт" вызвала всего лишь попытка перейти на GCC 4.4


Так GCC 4.4 уже под GPLv3 идет, разве не этот факт вызвал "дефолт"?
Обязательно бахнем! И не раз. Весь мир в труху! Но потом. (ДМБ)
Re[12]: оффтоп+volatile
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 14.09.09 15:26
Оценка: 4 (1)
Здравствуйте, ДимДимыч, Вы писали:

N>>какой "дефолт" вызвала всего лишь попытка перейти на GCC 4.4

ДД>Так GCC 4.4 уже под GPLv3 идет, разве не этот факт вызвал "дефолт"?

Нет, речь о другом. Linux ломается с каждой новой версией, потому что что-то в мелочах меняется (чаще всего стили оптимизации), но в случае 4.4 количество поломок было особенно крупным.

Я разверну мысль из предыдущих сообщений. Отсутствие volatile склоняет компилятор к возможности оптимизировать доступ к переменной его кэшированием (прочитать или записать меньшее количество раз, чем надо), но есть чёткие границы, через которые он не переходит. Это границы тел функций (не в случае инлайнинга) и границы — вызовы функций, но второе — только если он гарантированно не может сам понять, что происходит в этой функции.

Представим себе систему с кооперативной многозадачностью, когда запрет переключения реализуется глобальной переменной. Если установка и снятие такого "лока" сделано напрямую в функции, то компилятор вполне вероятно захочет это оптимизировать. Это код следующего вида:

extern volatile nosched;

...
  foo = 1;
  nosched = 0; // разрешили переключение
  *bar = 1; // что-то снаружи стронули и возможно нам дали другие данные
  nosched = 1; // запретили переключение - работаем с данными
  if (foo != 3) {
    ...
  }


тут компилятор захочет оптимизировать и наверняка это сделает — именно foo ничем не защищено против этого.

А вот тут мы ему явно ставим барьер:

  foo = 1;
  unlock_scheduler();
  *bar = 1;
  lock_scheduler();
  if (foo != 3) {
    ...
  }


Главное здесь — чтобы компилятор при сборке данного модуля _не знал_, что такое эти lock_scheduler() и unlock_scheduler(), или для них была явная метка "что-то меняют, а что — ХЗ" (как в asm volatile). Тогда компилятор остережётся кэшировать foo — он не знает, менялось оно в этих вызовах или нет. Незнание реализуется ссылкой на другой модуль и его отделённой компиляцией.

(UPDATE: ещё штатный метод для gcc:
asm volatile("":::"memory")

— явное указание, что встраиваемый код изменил содержимое памяти и нельзя через его границу оптимизировать доступ. Должны быть аналоги для других компиляторов.)

Именно это и является той защитой, которая не позволяет коду в Linux сходить с ума и которая работает и против gcc, и вообще против любого нормального компилятора. И, как видим, в том тексте про это — ни слова. Есть, однако, одна фраза про "it acts as a memory barrier", но она неоднозначна: понятие memory barrier давно закреплено за другим явлением — средствами предотвращения нежелательного переупорядочения операций процессора. И при этом не сказано, что защитой против оптимизации компилятором является любая функция неизвестного компилятору смысла, а не только функции работы с семафорами и спинлоками (хотя именно эти содержат полный комплект необходимых защит, не только против компиляторной оптимизации).

Ну а почему я это связал с версией gcc — потому что качество кода и качество документации тут примерно одинаково по сути, а именно — резко неровно. Рядом с идеальным результатом может находиться кусок гнилого бреда. На что собственно и нарвались в обоих случаях.

Фух... надеюсь, теперь понятно.;)
The God is real, unless declared integer.
Отредактировано 17.05.2015 13:43 netch80 (+ gcc asm memory clobber) . Предыдущая версия .
Re[13]: оффтоп+volatile
От: ДимДимыч Украина http://klug.org.ua
Дата: 14.09.09 16:02
Оценка:
Здравствуйте, netch80, Вы писали:

N>Фух... надеюсь, теперь понятно.


Техническая сторона вопроса и раньше была понятна, я не отрицаю существования такой проблемы, но считаю, что метод ее решения — использование volatile — в общем случае неприемлим, и применять его "на всякий случай" неправильно.
Оптимизация, производимая компилятором — вторична, программиста не должно заботить, где находятся промежуточные результаты вычислений: в памяти ли или в регистрах. Нити одного процесса по определению выполняются в одном адресном пространстве, поэтому если первая нить выполняет:
...
foo = 1;
unlock();

а вторая:
lock();
use(foo);
...

и lock()/unlock() предназначены для синхронизации, то
1) use(foo) не должен выполниться ранее, чем отработает unlock()
2) foo должна содержать корректное значение, присвоенное перед unlock().
Иначе теряется смысл как нитевой модели, так и присвоения значений переменным вцелом.
И как работать в мультинитевой среде с более сложными структурами данных? Представляешь, во что выльется объявление volatile A в чем-то типа:
lock();
for (i=2; i<ARRAY_SIZE(A); i++)
{
    A[i] = (A[i-1]+A[i-2])/2;
}
signal();
unlock();

?
Или ради возможности оптимизации за счет кеширования результатов оптимизацию таких ситуаций возложить на программиста?
Обязательно бахнем! И не раз. Весь мир в труху! Но потом. (ДМБ)
Re[2]: volatile - нужен ли при pthread_mutex_lock() и в GTK?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 14.09.09 17:35
Оценка:
Здравствуйте, netch80, Вы писали:

N>Здравствуйте, riYu, Вы писали:


Y>>Собственно вопрос.


Y>>Если при доступе к переменной я окружаю код вызовами pthread_mutex_lock()/pthread_mutex_unlock(), то нужно ли при ее объявлении использовать квалификатор volatile?


N>В общем таки желательно.


Должен внести уточнение. При правильной реализации данных функций работы с мьютексом — не нужно. Собственно это всё рассказано в продолжении треда, но акцент тут надо поставить на реализации. Мне приходилось сталкиваться и с такими, где надо было это подпирать. Сейчас вроде бы в большинстве реализаций этой проблемы уже нет.

Собственно я "вспомнил" опыт с такими странными средами и поэтому взвился. В корректной реализации есть таки адекватные защиты и можно не бояться.

Y>>Где-то слышал, что компиляторы гарантируют, что после вызова функции в регистрах не окажется закэшированного значения переменной, но не уверен, так ли это.

N>Это так. Но вообще-то если Вы про "закэшированное", то связь с функциями lock/unlock тут иная. Не буду сильно вдаваться в подробности, но есть вопрос переупорядочения машинных команд по желанию процессора, и взятие лока должно быть гарантированно отработано до работы с данными, защищёнными этим локом, а освобождение лока — после такой работы. Думаю, Вам вспомнилось что-то из этой области.

Аналогично предыдущему. Вызов внешней функции влияет на компилятор, а её реализация — на процессор, если это ему явно нужно указывать.

Y>> Да и даже если так, то что тогда произойдет, если pthread_mutex_lock() — inline-функция или макроопределение?

N>А какая нафиг разница? pthread_mutex_lock — нечто, что обязано выполнить операцию захвата мьютекса до начала выполнения кода за ним.

И разница таки есть, в случае макроопределения и прочих вариантов требуется какой-то иной метод поставить компилятору барьер кэширования (AFAIR, в gcc на это годится asm volatile).

Y>>Вообще, судя по http://alenacpp.blogspot.com/2006/04/volatile.html#comment-1976129473176086084 volatile все-таки стоит ставить.

N>Угумс.

По сумме треда — нет, смысла нет, кроме специфичных сред.
The God is real, unless declared integer.
Re[14]: оффтоп+volatile
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 14.09.09 17:37
Оценка:
Здравствуйте, ДимДимыч, Вы писали:

ДД>Здравствуйте, netch80, Вы писали:


N>>Фух... надеюсь, теперь понятно.;)


ДД>Техническая сторона вопроса и раньше была понятна, я не отрицаю существования такой проблемы, но считаю, что метод ее решения — использование volatile — в общем случае неприемлим, и применять его "на всякий случай" неправильно.


Он неприемлем только потому, что отделяет любой доступ к переменной. Если бы он проводил логическую границу только в указанном месте — такой бы проблемы не было. Костыль есть, но он хорошо замаскирован.

Я там дописал уточнение к своему базовому сообщению в треде. В правильно рассчитанных современных средах volatile для межтредового доступа таки не нужен. Для большинства этого достаточно. А если где не так — надеюсь, там документация об этом скажет.
The God is real, unless declared integer.
Re[3]: volatile - нужен ли при pthread_mutex_lock() и в GTK?
От: riYu  
Дата: 14.09.09 19:07
Оценка:
Спасибо большое, теперь все понятно. Внимательно слежу за вашим с ДимДимычем обсуждением.
Re[5]: volatile - нужен ли при pthread_mutex_lock() и в GTK?
От: Аноним  
Дата: 14.09.09 22:00
Оценка:
Здравствуйте, Zhendos, Вы писали:

Z>Здравствуйте, ДимДимыч, Вы писали:


ДД>>Почему? Можно пример, когда pthread_mutex_lock()/pthread_mutex_unlock() без volatile недостаточно для обеспечения атомарности?


Z>например:


Z>
Z>#include <pthread.h>

Z>  static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
Z>  static int acquires_count = 0;

Z>  int
Z>  trylock()
Z>  {
Z>    int res;

Z>    res = pthread_mutex_trylock(&mutex);
Z>    if (res == 0)
Z>      ++acquires_count;

Z>    return res;
Z>  }
Z>


Z>подробности можно найти здесь:

Z>http://lkml.org/lkml/2007/10/24/673

Хм. Ну да, втыкание volatile "выключает" возможные оптимизации компилятора. И если в оптимизации конкректного компилятора, конкретной версии, с какими-то ключами возможны глюки и их нужно прибивать использованием volatile, то это как-то не тянет на полноценный пример.

Это всего лишь демонстрация бага одного компилятора...
Re[6]: volatile - нужен ли при pthread_mutex_lock() и в GTK?
От: Аноним  
Дата: 15.09.09 05:45
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, Zhendos, Вы писали:


А>Это всего лишь демонстрация бага одного компилятора...


если бы вы прочитали обсуждение по ссылкам, то узнали бы, что этот баг
возник из-за того в стандарте C/C++ никак не оговорена многопоточность,
и в соотвествие со стандартом любой компилятор может создать такой код,
что глюки в многопоточной программме потом замучаешься выгребать,

так что если не заморачиваться конкретным компилятором, а писать код в соотвествии со
стандартом, то лучше многопоточность вообще не использовать, до выхода новой редакции стандарта.
Re[7]: volatile - нужен ли при pthread_mutex_lock() и в GTK?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 15.09.09 08:48
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, Аноним, Вы писали:


А>>Здравствуйте, Zhendos, Вы писали:


А>>Это всего лишь демонстрация бага одного компилятора...


А>если бы вы прочитали обсуждение по ссылкам, то узнали бы, что этот баг

А>возник из-за того в стандарте C/C++ никак не оговорена многопоточность,
А>и в соотвествие со стандартом любой компилятор может создать такой код,
А>что глюки в многопоточной программме потом замучаешься выгребать,

А если бы Вы прочитали обсуждение, то увидели бы, что не всё так мрачно, и от того, что этот вопрос решается не "по стандарту" а "по жизни" — проблемы тем не менее уходят и можно работать даже с текущими версиями.

А>так что если не заморачиваться конкретным компилятором, а писать код в соотвествии со

А>стандартом, то лучше многопоточность вообще не использовать, до выхода новой редакции стандарта.

Приближающийся стандарт посвящён далеко не только этому, и если бы проблема была только в кэшировании компилятором в многонитевой среде — то он вообще был бы не нужен. В стандарте же основной акцент идёт на проблему синхронизации работы в процессорах в многоядерной/многопроцессорной среде.

И я бы настоятельно попросил не употреблять таких кривых переводов, как "многопоточность". Thread — это нить. Multithreading — многонитевость. В Unix мире принято именно так. Виндовые замашки, где то, что понятие "поток" имеет 4 разных значения, игнорируется — это не для нас.
The God is real, unless declared integer.
Re[7]: volatile - нужен ли при pthread_mutex_lock() и в GTK?
От: Аноним  
Дата: 15.09.09 23:59
Оценка:
Здравствуйте, Аноним, Вы писали:

А>>Это всего лишь демонстрация бага одного компилятора...


А>если бы вы прочитали обсуждение по ссылкам, то узнали бы, что этот баг

А>возник из-за того в стандарте C/C++ никак не оговорена многопоточность,
А>и в соотвествие со стандартом любой компилятор может создать такой код,
А>что глюки в многопоточной программме потом замучаешься выгребать,

Если есть код:
int var;
/* ... */
if (cond)
    var++;


То я ожидаю, что компилятор сделает так, как я написал, а не так:
int var;
/* ... */
{
    register int var_tmp;

    var_tmp = var;

    if (cond)
        var_tmp++;

    var = var_tmp;
}


А>так что если не заморачиваться конкретным компилятором, а писать код в соотвествии со

А>стандартом, то лучше многопоточность вообще не использовать, до выхода новой редакции стандарта.

Да, я знаю, что компиляторы не идеальны и помимо генерации неверного кода при включенной оптимизации иногда даже валятся где-то у себя внутри. И общепринятый способ — это использовать volatile для затыкания всех возможных багов, вызванных оптимизацией.

Я не разделяю вашей позиции, оправдывающей баги в компиляторах. В примере выше проблема может проявиться не только в многопоточной программе (к примеру, var расположенна во флеше embedded устройства, и служит для подсчёта очень и очень редких аппаратных ошибок, и при написании программы было учтено количество циклов перезаписи флеша, а у нас переменная будет перезаписываться каждый раз, и флеш быстро умрёт), так что спихивать все проблемы на отсутсвие в стандартах четкого описания поведения компилятора в случае многопоточных приложений — по меньшей мере глупо.
Re[8]: volatile - нужен ли при pthread_mutex_lock() и в GTK?
От: ДимДимыч Украина http://klug.org.ua
Дата: 16.09.09 08:14
Оценка: 2 (1)
Здравствуйте, Аноним, Вы писали:

А>к примеру, var расположенна во флеше embedded устройства, и служит для подсчёта очень и очень редких аппаратных ошибок, и при написании программы было учтено количество циклов перезаписи флеша, а у нас переменная будет перезаписываться каждый раз, и флеш быстро умрёт


Имхо не очень удачный пример. Как раз если работа с переменной (с областью памяти) вызывает побочные эффекты, которыми нельзя пренебрегать (ресурс eeprom, например, или доростоящий по энергопотреблению цикл записи на внешний носитель), то закладываться на то, что компилятор что-то заоптимизирует — тоже нельзя. В таких случаях нужно четко расписать обращение к "особым" переменным вручную.
Обязательно бахнем! И не раз. Весь мир в труху! Но потом. (ДМБ)
Re[9]: volatile - нужен ли при pthread_mutex_lock() и в GTK?
От: Аноним  
Дата: 16.09.09 09:02
Оценка:
Здравствуйте, ДимДимыч, Вы писали:

ДД>Здравствуйте, Аноним, Вы писали:


А>>к примеру, var расположенна во флеше embedded устройства, и служит для подсчёта очень и очень редких аппаратных ошибок, и при написании программы было учтено количество циклов перезаписи флеша, а у нас переменная будет перезаписываться каждый раз, и флеш быстро умрёт


ДД>Имхо не очень удачный пример. Как раз если работа с переменной (с областью памяти) вызывает побочные эффекты, которыми нельзя пренебрегать (ресурс eeprom, например, или доростоящий по энергопотреблению цикл записи на внешний носитель), то закладываться на то, что компилятор что-то заоптимизирует — тоже нельзя. В таких случаях нужно четко расписать обращение к "особым" переменным вручную.



Хорошо, мы сделаем функцию write_var() с телом, где идёт работа с переменной из eeproma, и разместим её в том же юните трансляции. Теперь включаем оптимизацию, а gcc инлайнит и делает то, что я привёл выше. Будет то же самое
Re[10]: volatile - нужен ли при pthread_mutex_lock() и в GTK
От: ДимДимыч Украина http://klug.org.ua
Дата: 16.09.09 09:28
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Хорошо, мы сделаем функцию write_var() с телом, где идёт работа с переменной из eeproma, и разместим её в том же юните трансляции.


Как раз для таких случаев volatile и существует. Объявляем переменную в eeprom'е как volatile, все предварительные вычисления производим с обычными переменными (пусть gcc их оптимизирует, как хочет), а в конце явно инициируем запись результата в volatile-переменную. Мы будем уверены, что при присвоении значения в тексте программы произойдет цикл записи в устройство, и именно тогда, когда нам это будет нужно, не больше и не меньше.

А>Теперь включаем оптимизацию, а gcc инлайнит и делает то, что я привёл выше. Будет то же самое


Если по каким-то причинам функцию нельзя инлайнить, то в gcc для этого есть специальные аттрибуты. Но к рассматриваемому случаю с eeprom это не относится.
Обязательно бахнем! И не раз. Весь мир в труху! Но потом. (ДМБ)
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.