- 01
- 02
- 03
- 04
- 05
- 06
- 07
- 08
- 09
- 10
- 11
- 12
- 13
- 14
- 15
class SpinLock
{
std::atomic_flag lck;
public:
SpinLock(){
unlock();
}
__forceinline void lock(){
while (lck.test_and_set(std::memory_order_acquire)){
}
}
__forceinline void unlock(){
lck.clear(std::memory_order_release);
}
};
LispGovno 22.02.2015 11:29 # 0
bormand 22.02.2015 11:34 # 0
LispGovno 22.02.2015 12:22 # 0
Не юзает готовый спинлок.
Не юзает _mm_pause() и __yield().
Нет проверки на тот случай что это одноядерный цпу.
Нет ухода в ядро с мьютексом (это уже другое) или слипом (а это еще спинлок), на тот случай, если операция сильно задерживается.
bormand 22.02.2015 13:12 # +1
А конструктор походу из-за того, что автор перебдел и испугался того, что не будет release happens before aquire на первом вызове lock(). Но оно там и не нужно, на самом то деле, если лок и защищаемые им данные не через анус публиковали.
LispGovno 22.02.2015 13:44 # 0
Dummy00001 22.02.2015 15:19 # +1
bormand 22.02.2015 15:32 # +2
kegdan 22.02.2015 15:33 # +2
Dummy00001 22.02.2015 15:51 # +1
bormand 22.02.2015 16:03 # +1
3.14159265 22.02.2015 16:20 # +1
Решил обёртку указателя на null, повторно юзать...
Но c GC тут надо постараться, да начать делать «оптимизации».
Dummy00001 22.02.2015 20:01 # 0
это чего за проблема? рэйс кондишн?
bormand 23.02.2015 11:40 # +3
kegdan 23.02.2015 11:47 # +2
LispGovno 23.02.2015 11:48 # 0
Тред выделил указатель и записал в вар. Второй тред освободил указатель и записал в вар. Это состояние прочитал третий тред и начал вычисления. И тут внезапно 4тый тред записал не нулевой указатель в вар. 5тый тред освободил указатель в вар и записал туда нул_птр. Третий поток проснулся и пони, что в вар лежит нул и ничего не изменилось и 4тый и 5тый поток в вар ничего не записывали и он как-бы не прав. Другие виды абу в сосаче# не возможны.
3.14159265 23.02.2015 15:11 # 0
> сосаче#
Чё7
Я использовал одну обёртку над nullом на всю систему - N, полагая - нахера плодить лишние объекты.
Первый поток пытается прочитать ячейку и указатель, видя что в ячейке N (пусто) он ложит данные X - CAS(X,N) и инкрементит указатель.
В это же время (когда первый уже прочитал ячейку, но еще не начал туда писать) второй поток может уже положить туда Y, а третий после него прочитать Y и удалить его, записав единый на всю систему пустой объект N.
Тогда первый не заметит что в ячейке что-то произошло, поскольку там по-прежнему N и compareAndSet(X,N) нормально сработает, испортив указатель.
Если мы постоянно порождаем новые обёртки над null спинлок-транзакция первого потока обломится поскольку третий поток запишет туда N', compareAndSet(X,N) вернёт false.
Таким образом первый поток пойдёт в retry не солоно хлебавши.
Dummy00001 23.02.2015 14:07 # 0
bormand 23.02.2015 14:12 # +1
Dummy00001 23.02.2015 14:49 # 0
Elvenfighter 23.02.2015 01:27 # +1
LispGovno 23.02.2015 03:02 # 0
LispGovno 22.02.2015 13:45 # +1
Dummy00001 22.02.2015 15:18 # 0
> Не юзает готовый спинлок.
в С++ нет готового спинлока. в бусте - да.
> Не юзает _mm_pause() и __yield().
потому что это спинлок.
> Нет проверки на тот случай что это одноядерный цпу.
по барабану. взятие спинлока всегда успешно. в другой стороны: ты где последний раз одноядерный проц видел?
(я ща на встроенке работаю - у меня все одноядерное. но это однозначно исключение.)
> Нет ухода в ядро
потому что это спинлок.
код скорее всего говно потому что user-space спинлоки не нужны. на виндах есть critical sections, на линухах pthread_mutex (уход в ядро только на коллизии).
LispGovno 23.02.2015 02:48 # 0
> потому что это спинлок.
Лол что? Именно поэтому он и должен его юзать? Со слипом не путой. И да слип тоже должен юзать, только правильно
И вообще вместо цикла MONITOR бы заюзать или типа того
LispGovno 23.02.2015 02:51 # 0
Да ты поехавший. Многопоточности на одноядерном уже нет?
Dummy00001 23.02.2015 02:53 # +2
LispGovno 23.02.2015 03:01 # 0
LispGovno 23.02.2015 02:54 # 0
Возьми ты системную критическую секцию и не я би людям мозги.
Умных горе-оптимизаторов, придумывающих говнокод всегда было море. Грамотных оптимизаторов мало. В топике типичный случай быдлооптимизатора.
LispGovno 23.02.2015 02:55 # 0
Dummy00001 23.02.2015 03:03 # +1
с сигналами проблема в том что когда обработчик сигнала пытается взять спинлок, который уже взят в этом потоке, он само собой разумеется подвисает на ожидании освобождения спинлока. потому что он никогда не будет освобожден, потому что был прерван сигналом.
теоретически это обходится запрещением сигналов, но это относительно дорогие системные вызовы. (и их нужно два: во время взятия спинлока, и после освобождения.) поэтому спинлок на *нихах просто не имеет смысла: правильная реализация по производительности хуже обычного мутекса, которому нужен только один системный вызов.
LispGovno 23.02.2015 03:15 # 0
Dummy00001 23.02.2015 03:25 # 0
это просто добавляет гемороя.
на линухах и прочем, в тонких местах я народ на семафоры пересаживал, потому что гарантировано работают с сигналами.
в общем случае все эти извраты просто не нужны.
LispGovno 23.02.2015 03:33 # 0
Dummy00001 23.02.2015 04:31 # +1
на солярке если память не изменяет SA_RESTART тоже работал без проблем.
а вот на AIX это была просто реальная жопа... там все под ряд EINTR возвращает, даже включая неблокирующие вызовы.
LispGovno 23.02.2015 11:51 # 0
> большинство системных вызовов либо libc. либо кернелом рестартуются автоматом.
Доверять господину случаю не привык
Dummy00001 23.02.2015 14:20 # +1
на линухе про это лучше вообще не думать: все что можно рестартовать, скорее всего рестартуется (была пара неочивидных исключений, но я их уже забыл). вызовы которые не могут быть просто так еще раза вызваны - например с таймаутами (select(), poll()) - скорее всего вернут EINTR.
LispGovno 23.02.2015 14:34 # 0
Dummy00001 23.02.2015 14:59 # 0
при апдейте две тонкости:
1. старый файл нужно удалить. переименовывать и прочее нельзя. все программы у которых есть этот файл открытый, будут ссылатся на старую (уже удаленную) версию.
2. после апдейта библиотеки (части группы библиотек) которая динамически подгружается, прога в памяти, которая держит старые версии либ, пытается зачитать новую версию либы, и слетает на проверке версий и/или интерфейсов.
№2 на нормальных стабильных дистро этого не должно происходить, потому что интерфейсы стабильные. №1 может случатся только для файлов которые пользователь менять не может ака read-only (потому что пакеты не должны включать пользовательских файлов).
LispGovno 23.02.2015 03:00 # 0
Нооооу. У меня Багратион от этих заявлений. Единственное свойство спинлока - это цикл с проверкой флага. Остальные характеристики, хоть уход в ядро - это не более чем свойства
Кстати офтоп: от всяких атомарных операций у многоядерного проца Бомбит, если их юзать на одну переменную (и даже кешлинию) одновременно и троугхпут сильно садится. Хотябы поэтому юзают слип в ядро. такие дела. сильно оптимизирует
Dummy00001 23.02.2015 03:13 # 0
> Кстати офтоп
это только если у тебя код ими повсеместно напичкан. насколько долго операции синхронизации относительно редки, это будет быстрее системного вызова.
все эти оптимизации как правило делаются для случая когда коллизии происходят редко. я на линухах тестировал - смысла извращатся мало потому что мутексы без коллизий достаточно быстрые.
в добавок, вроде интел грозился добавить acquire/release семантику для атомарных операций (на итаниках это дело было с самого начала, но не в x64). вроде бы даже в с++11 это дело было включено?
LispGovno 23.02.2015 03:17 # 0
мол встроенные фенсы на атомиках?
Dummy00001 23.02.2015 03:25 # 0
acquire/release, по крайней мере на итаниках, это просто хинты процу. типичный пример это тот же спинлок: взятие это acquire, освобождение это release. хинт acquire говорит процу что ты еще что-то будет делать с этой переменной в ближайшем будущем - а именно, освобождение спинлока. хинт release говорит что поток закончил с этой переменной работать, т.е. после освобождения эта переменна скорее всего потоку больше не нужна.
я делал простые тесты и честно говоря большой разницы в производительности от этих хинтов я не заметил.
LispGovno 23.02.2015 11:52 # 0
bormand 23.02.2015 12:10 # 0
Чтения/записи, стоящие справа от aquire не могут перепрыгнуть через него налево. А стоящие слева от release - направо. В итоге чтения/записи, исполняемые под локом (который, как мы знаем, начинается с aquire а заканчивается release), никуда из-под него не убегут.
А фенс это, грубо говоря, маркер, запрещающий переставлять через него чтения/записи. И, иногда, сбрасывающий кеши.
P.S. Я сам в это толком не въезжаю на низком уровне...
LispGovno 23.02.2015 12:18 # 0
Не не не братюнь.
акьюре делает все ЗАПИСИ произведенные в другом потоке с фенсом релиз или выше к данному моменту видными после данного фенса в данном потоке
релис публикует все ЗАПИСИ произведенные в данном потоке данного фенса, чтобы они были видны к данному моменту в потоках с фенсом акьюре или выше.
+ понятие "или выше" несколько сложнее, чем может показаться в первый раз.
bormand 23.02.2015 12:31 # 0
Ну тогда не ВСЕ, а только те, которые были перед последним release фенсом в тех потоках. Эффект от остальных он может не видеть, может видеть, а может вообще кусками.
bormand 23.02.2015 12:35 # 0
bormand 23.02.2015 12:50 # +2
А пока нашёл и раскуриваю вот эту статью. Тут вроде по хардкору поясняют про когерентность кешей, барьеры и прочую низкоуровневую магию 90лвл.
Xom94ok 23.02.2015 13:45 # +3
http://people.freebsd.org/~lstewart/articles/cpumemory.pdf
Перевод тоже где-то валяется, но без пдф. Я понял, как делать неразрывные сслыки, уииии :)
bormand 23.02.2015 14:15 # +2
LispGovno 23.02.2015 14:31 # 0
А вообще фенсы и локи не нужны. Только стм/хтм (который закрыли все разработчики с концами) или агенты/акторы.
Dummy00001 23.02.2015 14:40 # 0
но я все мелочи пытаюсь к атомикам сводить. если атомики не катят - то тогда нормальный локинг без извратов.
в паре мест еще до меня герои пыталить извратов наворотить (и локинг просто интерфейс не позволял) и приходилось фиксить и извращатся.
Xom94ok 23.02.2015 14:55 # 0
ХЗ, может быть. Я до сих пор не могу сходу представить, где лево, а где право или всегда представляю карту, когда нужно узнать, в какой стороне находится запад, а в какой - восток. С этими фенсами та же ситуация. Есть операции с ордерингом acquire, release, relaxed, seq_cst. Какая операция и куда не пропускает чтение или запись? И почему они названы именно так, а не исходя из запрета на какой-либо reordering? А если операция - это инкремент, который включает в себя и чтение, и запись, то какой смысл имеет ордеринг? Вопросов получается больше, чем документация предоставляет.
bormand 23.02.2015 16:56 # +1
LispGovno 23.02.2015 21:17 # 0
bormand 24.02.2015 05:20 # 0
LispGovno 24.02.2015 07:55 # 0
bormand 24.02.2015 08:04 # 0
Или ты про std'шные фенсы, а не про процессорные?
LispGovno 24.02.2015 09:23 # 0
bormand 24.02.2015 09:47 # 0
Пруф в студию!
LispGovno 24.02.2015 10:55 # 0
bormand 24.02.2015 11:23 # 0
Т.е. в Стандарте то же самое написано?
LispGovno 24.02.2015 22:00 # 0
bormand 24.02.2015 08:51 # 0
bormand 23.02.2015 14:25 # 0
Даже пыхопыхник?
Xom94ok 23.02.2015 14:44 # +2
> Даже пыхопыхник?
Почему бы и нет? Может быть, это спасет его от дальнейшего развития как пыхопышника.
absolut 22.02.2015 20:29 # +3
bormand 24.02.2015 13:00 # +1
Впрочем, это может быть и фичей.
LispGovno 24.02.2015 21:59 # 0
bormand 25.02.2015 06:15 # 0
В других случаях может и интересно будет. Вроде бы в double-checked синглтоне такую фишку с relaxed'ом и отдельным фенсом в успешной ветке юзали.
bormand 25.02.2015 07:00 # 0
Компьютер-расист.
kegdan 25.02.2015 07:23 # +1
Внимание! Ваш код не будет скомпилен, так как вы не собрали весь хлопок