- 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);
}
};
Не юзает готовый спинлок.
Не юзает _mm_pause() и __yield().
Нет проверки на тот случай что это одноядерный цпу.
Нет ухода в ядро с мьютексом (это уже другое) или слипом (а это еще спинлок), на тот случай, если операция сильно задерживается.
А конструктор походу из-за того, что автор перебдел и испугался того, что не будет release happens before aquire на первом вызове lock(). Но оно там и не нужно, на самом то деле, если лок и защищаемые им данные не через анус публиковали.
Решил обёртку указателя на null, повторно юзать...
Но c GC тут надо постараться, да начать делать «оптимизации».
это чего за проблема? рэйс кондишн?
Тред выделил указатель и записал в вар. Второй тред освободил указатель и записал в вар. Это состояние прочитал третий тред и начал вычисления. И тут внезапно 4тый тред записал не нулевой указатель в вар. 5тый тред освободил указатель в вар и записал туда нул_птр. Третий поток проснулся и пони, что в вар лежит нул и ничего не изменилось и 4тый и 5тый поток в вар ничего не записывали и он как-бы не прав. Другие виды абу в сосаче# не возможны.
> сосаче#
Чё7
Я использовал одну обёртку над nullом на всю систему - N, полагая - нахера плодить лишние объекты.
Первый поток пытается прочитать ячейку и указатель, видя что в ячейке N (пусто) он ложит данные X - CAS(X,N) и инкрементит указатель.
В это же время (когда первый уже прочитал ячейку, но еще не начал туда писать) второй поток может уже положить туда Y, а третий после него прочитать Y и удалить его, записав единый на всю систему пустой объект N.
Тогда первый не заметит что в ячейке что-то произошло, поскольку там по-прежнему N и compareAndSet(X,N) нормально сработает, испортив указатель.
Если мы постоянно порождаем новые обёртки над null спинлок-транзакция первого потока обломится поскольку третий поток запишет туда N', compareAndSet(X,N) вернёт false.
Таким образом первый поток пойдёт в retry не солоно хлебавши.
> Не юзает готовый спинлок.
в С++ нет готового спинлока. в бусте - да.
> Не юзает _mm_pause() и __yield().
потому что это спинлок.
> Нет проверки на тот случай что это одноядерный цпу.
по барабану. взятие спинлока всегда успешно. в другой стороны: ты где последний раз одноядерный проц видел?
(я ща на встроенке работаю - у меня все одноядерное. но это однозначно исключение.)
> Нет ухода в ядро
потому что это спинлок.
код скорее всего говно потому что user-space спинлоки не нужны. на виндах есть critical sections, на линухах pthread_mutex (уход в ядро только на коллизии).
> потому что это спинлок.
Лол что? Именно поэтому он и должен его юзать? Со слипом не путой. И да слип тоже должен юзать, только правильно
И вообще вместо цикла MONITOR бы заюзать или типа того
Да ты поехавший. Многопоточности на одноядерном уже нет?
Возьми ты системную критическую секцию и не я би людям мозги.
Умных горе-оптимизаторов, придумывающих говнокод всегда было море. Грамотных оптимизаторов мало. В топике типичный случай быдлооптимизатора.
с сигналами проблема в том что когда обработчик сигнала пытается взять спинлок, который уже взят в этом потоке, он само собой разумеется подвисает на ожидании освобождения спинлока. потому что он никогда не будет освобожден, потому что был прерван сигналом.
теоретически это обходится запрещением сигналов, но это относительно дорогие системные вызовы. (и их нужно два: во время взятия спинлока, и после освобождения.) поэтому спинлок на *нихах просто не имеет смысла: правильная реализация по производительности хуже обычного мутекса, которому нужен только один системный вызов.
это просто добавляет гемороя.
на линухах и прочем, в тонких местах я народ на семафоры пересаживал, потому что гарантировано работают с сигналами.
в общем случае все эти извраты просто не нужны.
на солярке если память не изменяет SA_RESTART тоже работал без проблем.
а вот на AIX это была просто реальная жопа... там все под ряд EINTR возвращает, даже включая неблокирующие вызовы.
> большинство системных вызовов либо libc. либо кернелом рестартуются автоматом.
Доверять господину случаю не привык
на линухе про это лучше вообще не думать: все что можно рестартовать, скорее всего рестартуется (была пара неочивидных исключений, но я их уже забыл). вызовы которые не могут быть просто так еще раза вызваны - например с таймаутами (select(), poll()) - скорее всего вернут EINTR.
при апдейте две тонкости:
1. старый файл нужно удалить. переименовывать и прочее нельзя. все программы у которых есть этот файл открытый, будут ссылатся на старую (уже удаленную) версию.
2. после апдейта библиотеки (части группы библиотек) которая динамически подгружается, прога в памяти, которая держит старые версии либ, пытается зачитать новую версию либы, и слетает на проверке версий и/или интерфейсов.
№2 на нормальных стабильных дистро этого не должно происходить, потому что интерфейсы стабильные. №1 может случатся только для файлов которые пользователь менять не может ака read-only (потому что пакеты не должны включать пользовательских файлов).
Нооооу. У меня Багратион от этих заявлений. Единственное свойство спинлока - это цикл с проверкой флага. Остальные характеристики, хоть уход в ядро - это не более чем свойства
Кстати офтоп: от всяких атомарных операций у многоядерного проца Бомбит, если их юзать на одну переменную (и даже кешлинию) одновременно и троугхпут сильно садится. Хотябы поэтому юзают слип в ядро. такие дела. сильно оптимизирует
> Кстати офтоп
это только если у тебя код ими повсеместно напичкан. насколько долго операции синхронизации относительно редки, это будет быстрее системного вызова.
все эти оптимизации как правило делаются для случая когда коллизии происходят редко. я на линухах тестировал - смысла извращатся мало потому что мутексы без коллизий достаточно быстрые.
в добавок, вроде интел грозился добавить acquire/release семантику для атомарных операций (на итаниках это дело было с самого начала, но не в x64). вроде бы даже в с++11 это дело было включено?
мол встроенные фенсы на атомиках?
acquire/release, по крайней мере на итаниках, это просто хинты процу. типичный пример это тот же спинлок: взятие это acquire, освобождение это release. хинт acquire говорит процу что ты еще что-то будет делать с этой переменной в ближайшем будущем - а именно, освобождение спинлока. хинт release говорит что поток закончил с этой переменной работать, т.е. после освобождения эта переменна скорее всего потоку больше не нужна.
я делал простые тесты и честно говоря большой разницы в производительности от этих хинтов я не заметил.
Чтения/записи, стоящие справа от aquire не могут перепрыгнуть через него налево. А стоящие слева от release - направо. В итоге чтения/записи, исполняемые под локом (который, как мы знаем, начинается с aquire а заканчивается release), никуда из-под него не убегут.
А фенс это, грубо говоря, маркер, запрещающий переставлять через него чтения/записи. И, иногда, сбрасывающий кеши.
P.S. Я сам в это толком не въезжаю на низком уровне...
Не не не братюнь.
акьюре делает все ЗАПИСИ произведенные в другом потоке с фенсом релиз или выше к данному моменту видными после данного фенса в данном потоке
релис публикует все ЗАПИСИ произведенные в данном потоке данного фенса, чтобы они были видны к данному моменту в потоках с фенсом акьюре или выше.
+ понятие "или выше" несколько сложнее, чем может показаться в первый раз.
Ну тогда не ВСЕ, а только те, которые были перед последним release фенсом в тех потоках. Эффект от остальных он может не видеть, может видеть, а может вообще кусками.
А пока нашёл и раскуриваю вот эту статью. Тут вроде по хардкору поясняют про когерентность кешей, барьеры и прочую низкоуровневую магию 90лвл.
http://people.freebsd.org/~lstewart/articles/cpumemory.pdf
Перевод тоже где-то валяется, но без пдф. Я понял, как делать неразрывные сслыки, уииии :)
А вообще фенсы и локи не нужны. Только стм/хтм (который закрыли все разработчики с концами) или агенты/акторы.
но я все мелочи пытаюсь к атомикам сводить. если атомики не катят - то тогда нормальный локинг без извратов.
в паре мест еще до меня герои пыталить извратов наворотить (и локинг просто интерфейс не позволял) и приходилось фиксить и извращатся.
ХЗ, может быть. Я до сих пор не могу сходу представить, где лево, а где право или всегда представляю карту, когда нужно узнать, в какой стороне находится запад, а в какой - восток. С этими фенсами та же ситуация. Есть операции с ордерингом acquire, release, relaxed, seq_cst. Какая операция и куда не пропускает чтение или запись? И почему они названы именно так, а не исходя из запрета на какой-либо reordering? А если операция - это инкремент, который включает в себя и чтение, и запись, то какой смысл имеет ордеринг? Вопросов получается больше, чем документация предоставляет.
Или ты про std'шные фенсы, а не про процессорные?
Пруф в студию!
Т.е. в Стандарте то же самое написано?
Даже пыхопыхник?
> Даже пыхопыхник?
Почему бы и нет? Может быть, это спасет его от дальнейшего развития как пыхопышника.
Впрочем, это может быть и фичей.
В других случаях может и интересно будет. Вроде бы в double-checked синглтоне такую фишку с relaxed'ом и отдельным фенсом в успешной ветке юзали.
Компьютер-расист.
Внимание! Ваш код не будет скомпилен, так как вы не собрали весь хлопок