Здравствуйте, ДимДимыч, Вы писали:
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 ничем не защищено против этого.
Главное здесь — чтобы компилятор при сборке данного модуля _не знал_, что такое эти lock_scheduler() и unlock_scheduler(), или для них была явная метка "что-то меняют, а что — ХЗ" (как в asm volatile). Тогда компилятор остережётся кэшировать foo — он не знает, менялось оно в этих вызовах или нет. Незнание реализуется ссылкой на другой модуль и его отделённой компиляцией.
(UPDATE: ещё штатный метод для gcc:
asm volatile("":::"memory")
— явное указание, что встраиваемый код изменил содержимое памяти и нельзя через его границу оптимизировать доступ. Должны быть аналоги для других компиляторов.)
Именно это и является той защитой, которая не позволяет коду в Linux сходить с ума и которая работает и против gcc, и вообще против любого нормального компилятора. И, как видим, в том тексте про это — ни слова. Есть, однако, одна фраза про "it acts as a memory barrier", но она неоднозначна: понятие memory barrier давно закреплено за другим явлением — средствами предотвращения нежелательного переупорядочения операций процессора. И при этом не сказано, что защитой против оптимизации компилятором является любая функция неизвестного компилятору смысла, а не только функции работы с семафорами и спинлоками (хотя именно эти содержат полный комплект необходимых защит, не только против компиляторной оптимизации).
Ну а почему я это связал с версией gcc — потому что качество кода и качество документации тут примерно одинаково по сути, а именно — резко неровно. Рядом с идеальным результатом может находиться кусок гнилого бреда. На что собственно и нарвались в обоих случаях.
Здравствуйте, netch80, Вы писали:
N>Фух... надеюсь, теперь понятно.
Техническая сторона вопроса и раньше была понятна, я не отрицаю существования такой проблемы, но считаю, что метод ее решения — использование volatile — в общем случае неприемлим, и применять его "на всякий случай" неправильно.
Оптимизация, производимая компилятором — вторична, программиста не должно заботить, где находятся промежуточные результаты вычислений: в памяти ли или в регистрах. Нити одного процесса по определению выполняются в одном адресном пространстве, поэтому если первая нить выполняет:
...
foo = 1;
unlock();
а вторая:
lock();
use(foo);
...
и lock()/unlock() предназначены для синхронизации, то
1) use(foo) не должен выполниться ранее, чем отработает unlock()
2) foo должна содержать корректное значение, присвоенное перед unlock().
Иначе теряется смысл как нитевой модели, так и присвоения значений переменным вцелом.
И как работать в мультинитевой среде с более сложными структурами данных? Представляешь, во что выльется объявление volatile A в чем-то типа:
Здравствуйте, 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>Угумс.
По сумме треда — нет, смысла нет, кроме специфичных сред.
Здравствуйте, ДимДимыч, Вы писали:
ДД>Здравствуйте, netch80, Вы писали:
N>>Фух... надеюсь, теперь понятно.;)
ДД>Техническая сторона вопроса и раньше была понятна, я не отрицаю существования такой проблемы, но считаю, что метод ее решения — использование volatile — в общем случае неприемлим, и применять его "на всякий случай" неправильно.
Он неприемлем только потому, что отделяет любой доступ к переменной. Если бы он проводил логическую границу только в указанном месте — такой бы проблемы не было. Костыль есть, но он хорошо замаскирован.
Я там дописал уточнение к своему базовому сообщению в треде. В правильно рассчитанных современных средах volatile для межтредового доступа таки не нужен. Для большинства этого достаточно. А если где не так — надеюсь, там документация об этом скажет.
The God is real, unless declared integer.
Re[3]: volatile - нужен ли при pthread_mutex_lock() и в GTK?
Спасибо большое, теперь все понятно. Внимательно слежу за вашим с ДимДимычем обсуждением.
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>
Хм. Ну да, втыкание volatile "выключает" возможные оптимизации компилятора. И если в оптимизации конкректного компилятора, конкретной версии, с какими-то ключами возможны глюки и их нужно прибивать использованием volatile, то это как-то не тянет на полноценный пример.
Это всего лишь демонстрация бага одного компилятора...
Re[6]: volatile - нужен ли при pthread_mutex_lock() и в GTK?
От:
Аноним
Дата:
15.09.09 05:45
Оценка:
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, Zhendos, Вы писали:
А>Это всего лишь демонстрация бага одного компилятора...
если бы вы прочитали обсуждение по ссылкам, то узнали бы, что этот баг
возник из-за того в стандарте C/C++ никак не оговорена многопоточность,
и в соотвествие со стандартом любой компилятор может создать такой код,
что глюки в многопоточной программме потом замучаешься выгребать,
так что если не заморачиваться конкретным компилятором, а писать код в соотвествии со
стандартом, то лучше многопоточность вообще не использовать, до выхода новой редакции стандарта.
Re[7]: volatile - нужен ли при pthread_mutex_lock() и в GTK?
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, Аноним, Вы писали:
А>>Здравствуйте, 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?
Здравствуйте, Аноним, Вы писали:
А>к примеру, 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
Здравствуйте, Аноним, Вы писали:
А>Хорошо, мы сделаем функцию write_var() с телом, где идёт работа с переменной из eeproma, и разместим её в том же юните трансляции.
Как раз для таких случаев volatile и существует. Объявляем переменную в eeprom'е как volatile, все предварительные вычисления производим с обычными переменными (пусть gcc их оптимизирует, как хочет), а в конце явно инициируем запись результата в volatile-переменную. Мы будем уверены, что при присвоении значения в тексте программы произойдет цикл записи в устройство, и именно тогда, когда нам это будет нужно, не больше и не меньше.
А>Теперь включаем оптимизацию, а gcc инлайнит и делает то, что я привёл выше. Будет то же самое
Если по каким-то причинам функцию нельзя инлайнить, то в gcc для этого есть специальные аттрибуты. Но к рассматриваемому случаю с eeprom это не относится.
Обязательно бахнем! И не раз. Весь мир в труху! Но потом. (ДМБ)