1. C++ / Говнокод #17676

    +54

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 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 Февраля 2015

    Комментарии (72) RSS

    • О да, детка...
      Ответить
    • В чём говно? Ну написал свой мутекс спинлок ради интереса...
      Ответить
      • Конструктор с std::memory_order_release о_О.
        Не юзает готовый спинлок.
        Не юзает _mm_pause() и __yield().
        Нет проверки на тот случай что это одноядерный цпу.
        Нет ухода в ядро с мьютексом (это уже другое) или слипом (а это еще спинлок), на тот случай, если операция сильно задерживается.
        Ответить
        • Всё объясняется очень просто - кто-то решил пофаниться и написать простенький спинлок. Вот и всё.

          А конструктор походу из-за того, что автор перебдел и испугался того, что не будет release happens before aquire на первом вызове lock(). Но оно там и не нужно, на самом то деле, если лок и защищаемые им данные не через анус публиковали.
          Ответить
          • Автор сказал, что будет использовать для защиты установки флагов. Так что не пофаниться)))
            Ответить
            • атомики религия не позволяет?
              Ответить
              • Ну может ему 100500 флагов разом надо переключать?
                Ответить
                • Революция прям
                  Ответить
                • туше. флаги ложишь в структуру. переключаешь указатель на структуры. на большинстве платформ, для этого даже атомики не нужны, если структуры где-то лежат статически. с динамикой, нужен атомик для reference counter'а.
                  Ответить
                  • Не юзаешь GC @ переключаешь указатель @ ловишь ABA проблему. Но в общем-то приём хороший при разумном объеме данных которые редко обновляются. А ABA решаема.
                    Ответить
                    • Я и c GC ловил ABA.
                      Решил обёртку указателя на null, повторно юзать...
                      Но c GC тут надо постараться, да начать делать «оптимизации».
                      Ответить
                    • > ловишь ABA проблему

                      это чего за проблема? рэйс кондишн?
                      Ответить
                      • Короче один тред прочитал указатель на блок A и уснул. Второй в это время выделил новый блок (B), поменял указатель и освободил старый блок (A). Потом пришёл третий, выделил место под новый блок (ему достался тот же самый A) подменил указатель и грохнул B. Первый проснулся, и обрадовался что ничего не поменялось, указатель то такой же. Это и есть A->B->A проблема (подробнее на вики).
                        Ответить
                        • Я когда первое предложение прочитал, подумал, что борманд тоже присоединился к гильдии бредописцев.
                          Ответить
                        • Я так понял у пи был Абу с null_ptr.
                          Тред выделил указатель и записал в вар. Второй тред освободил указатель и записал в вар. Это состояние прочитал третий тред и начал вычисления. И тут внезапно 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 не солоно хлебавши.
                            Ответить
                        • а. под тормаживаю. одна часть меня говорит что не проблема - уже раз такое делал. вторая говорит что там может быть еще что-то было.
                          Ответить
                          • Ну мы же не можем атомарно {прочитать значение указателя и по этому указателю увеличить счетчик}...
                            Ответить
                            • да. скорее всего у меня там было статически сделано. там было разделение global vs per-thread структуры, и у всех потоков была своя копия почти всех глобальных данных.
                              Ответить
                    • Что-то я не в теме: GC в крестах?
                      Ответить
          • > aquire
            Ответить
        • это очевидно виндовый код. если не винды, то ГК в том что с *ниховыми сигналами у этого кода проблемы будут. но это наврядли *них, потому что тут легковесных синхронизаций хоть жопой жри.

          > Не юзает готовый спинлок.

          в С++ нет готового спинлока. в бусте - да.

          > Не юзает _mm_pause() и __yield().

          потому что это спинлок.

          > Нет проверки на тот случай что это одноядерный цпу.

          по барабану. взятие спинлока всегда успешно. в другой стороны: ты где последний раз одноядерный проц видел?

          (я ща на встроенке работаю - у меня все одноядерное. но это однозначно исключение.)

          > Нет ухода в ядро

          потому что это спинлок.

          код скорее всего говно потому что user-space спинлоки не нужны. на виндах есть critical sections, на линухах pthread_mutex (уход в ядро только на коллизии).
          Ответить
          • >> Не юзает _mm_pause() и __yield().
            > потому что это спинлок.
            Лол что? Именно поэтому он и должен его юзать? Со слипом не путой. И да слип тоже должен юзать, только правильно

            И вообще вместо цикла MONITOR бы заюзать или типа того
            Ответить
          • > по барабану. взятие спинлока всегда успешно.
            Да ты поехавший. Многопоточности на одноядерном уже нет?
            Ответить
            • бля. вот только полгода посидел на cooperative multitasking, и весь preemptive multi-tasking из головы вылетел...
              Ответить
          • > в С++ нет готового спинлока.
            Возьми ты системную критическую секцию и не я би людям мозги.
            Умных горе-оптимизаторов, придумывающих говнокод всегда было море. Грамотных оптимизаторов мало. В топике типичный случай быдлооптимизатора.
            Ответить
          • Какая проблема у сигналов с этим кодом?
            Ответить
            • я тормозил там cooperative vs preemptive multi-tasking.

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

              теоретически это обходится запрещением сигналов, но это относительно дорогие системные вызовы. (и их нужно два: во время взятия спинлока, и после освобождения.) поэтому спинлок на *нихах просто не имеет смысла: правильная реализация по производительности хуже обычного мутекса, которому нужен только один системный вызов.
              Ответить
              • а ведь можно не ставить обработчики, юзающие спинлоки. да и обработчиках сигналов можно юзать лок без спина (типа трай и если не удалось, о откладываем обработку в таймер или еще куда)
                Ответить
                • > (типа трай и если не удалось, о откладываем обработку в таймер или еще куда)

                  это просто добавляет гемороя.

                  на линухах и прочем, в тонких местах я народ на семафоры пересаживал, потому что гарантировано работают с сигналами.

                  в общем случае все эти извраты просто не нужны.
                  Ответить
                  • Как вообще можно жить, когда любая хрень может вернуть EINTR и нужно перевызывать функцию снова. Помоему это для копипастного или макросного быдла
                    Ответить
                    • на линухе к слову это не проблема - большинство системных вызовов либо libc, либо кернелом рестартуются автоматом.

                      на солярке если память не изменяет SA_RESTART тоже работал без проблем.

                      а вот на AIX это была просто реальная жопа... там все под ряд EINTR возвращает, даже включая неблокирующие вызовы.
                      Ответить
                      • preload += SA_RESTART в линакче тоже можно? А то что-то я не верю в
                        > большинство системных вызовов либо libc. либо кернелом рестартуются автоматом.
                        Доверять господину случаю не привык
                        Ответить
                        • SA_RESTART это флаг для sigction(). когда ставишь обработчик, можно сказать что бы libc рестартовала прерваные системные вызовы по выходу из сигнала.

                          на линухе про это лучше вообще не думать: все что можно рестартовать, скорее всего рестартуется (была пара неочивидных исключений, но я их уже забыл). вызовы которые не могут быть просто так еще раза вызваны - например с таймаутами (select(), poll()) - скорее всего вернут EINTR.
                          Ответить
                          • Я подозреваю проблема стала реже, но не убралась. Так что периодически линуксавые программы бомбит и они например не могут обновится корректно и иксы или еще что-нибудь после обновления и перезагрузки обязательно падают
                            Ответить
                            • это другая проблема. (на бубунту/дебиане я такого еще ни разу не видел.)

                              при апдейте две тонкости:
                              1. старый файл нужно удалить. переименовывать и прочее нельзя. все программы у которых есть этот файл открытый, будут ссылатся на старую (уже удаленную) версию.
                              2. после апдейта библиотеки (части группы библиотек) которая динамически подгружается, прога в памяти, которая держит старые версии либ, пытается зачитать новую версию либы, и слетает на проверке версий и/или интерфейсов.

                              №2 на нормальных стабильных дистро этого не должно происходить, потому что интерфейсы стабильные. №1 может случатся только для файлов которые пользователь менять не может ака read-only (потому что пакеты не должны включать пользовательских файлов).
                              Ответить
          • > потому что это спинлок.
            Нооооу. У меня Багратион от этих заявлений. Единственное свойство спинлока - это цикл с проверкой флага. Остальные характеристики, хоть уход в ядро - это не более чем свойства

            Кстати офтоп: от всяких атомарных операций у многоядерного проца Бомбит, если их юзать на одну переменную (и даже кешлинию) одновременно и троугхпут сильно садится. Хотябы поэтому юзают слип в ядро. такие дела. сильно оптимизирует
            Ответить
            • спинлоки это традиционно кернел - в юзерспэйсе с ними только грабли.

              > Кстати офтоп

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

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

              в добавок, вроде интел грозился добавить acquire/release семантику для атомарных операций (на итаниках это дело было с самого начала, но не в x64). вроде бы даже в с++11 это дело было включено?
              Ответить
              • > грозился добавить acquire/release семантику для атомарных операций
                мол встроенные фенсы на атомиках?
                Ответить
                • что такое фенсы я честно говоря так и не разобрался.

                  acquire/release, по крайней мере на итаниках, это просто хинты процу. типичный пример это тот же спинлок: взятие это acquire, освобождение это release. хинт acquire говорит процу что ты еще что-то будет делать с этой переменной в ближайшем будущем - а именно, освобождение спинлока. хинт release говорит что поток закончил с этой переменной работать, т.е. после освобождения эта переменна скорее всего потоку больше не нужна.

                  я делал простые тесты и честно говоря большой разницы в производительности от этих хинтов я не заметил.
                  Ответить
                  • фенс - забор
                    Ответить
                  • Ну не знаю как на итаниумах, но в c++11 и в других местах у aquire/release немножко другой смысл. Это хинты компилятору и процу, усмиряющие их любовь к оптимизации, кешированию и перестановке чтений/записей местами.

                    Чтения/записи, стоящие справа от aquire не могут перепрыгнуть через него налево. А стоящие слева от release - направо. В итоге чтения/записи, исполняемые под локом (который, как мы знаем, начинается с aquire а заканчивается release), никуда из-под него не убегут.

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

                    P.S. Я сам в это толком не въезжаю на низком уровне...
                    Ответить
                    • > Чтения/записи, стоящие справа от aquire не могут перепрыгнуть через него налево. А стоящие слева от release - направо.
                      Не не не братюнь.

                      акьюре делает все ЗАПИСИ произведенные в другом потоке с фенсом релиз или выше к данному моменту видными после данного фенса в данном потоке

                      релис публикует все ЗАПИСИ произведенные в данном потоке данного фенса, чтобы они были видны к данному моменту в потоках с фенсом акьюре или выше.

                      + понятие "или выше" несколько сложнее, чем может показаться в первый раз.
                      Ответить
                      • > акьюре делает все ЗАПИСИ
                        Ну тогда не ВСЕ, а только те, которые были перед последним release фенсом в тех потоках. Эффект от остальных он может не видеть, может видеть, а может вообще кусками.
                        Ответить
                      • Завтра буду за нормальным компом - попробую покомпилить разные лоады/сторы/фенсы под ARM. Посмотрим, чего gcc для него втыкает.
                        Ответить
                      • http://www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.07.23a.pdf

                        А пока нашёл и раскуриваю вот эту статью. Тут вроде по хардкору поясняют про когерентность кешей, барьеры и прочую низкоуровневую магию 90лвл.
                        Ответить
                        • Есть ещё более хардкорное чтиво:
                          http://people.freebsd.org/~lstewart/articles/cpumemory.pdf
                          Перевод тоже где-то валяется, но без пдф. Я понял, как делать неразрывные сслыки, уииии :)
                          Ответить
                          • Спасибо, добавил в read queue. Втопку переводы.
                            Ответить
                            • Чё так прогеры от фенсов батхертят? Простая же концепция.
                              А вообще фенсы и локи не нужны. Только стм/хтм (который закрыли все разработчики с концами) или агенты/акторы.
                              Ответить
                              • для мелочей которые хочется что бы оставались мелочами.

                                но я все мелочи пытаюсь к атомикам сводить. если атомики не катят - то тогда нормальный локинг без извратов.

                                в паре мест еще до меня герои пыталить извратов наворотить (и локинг просто интерфейс не позволял) и приходилось фиксить и извращатся.
                                Ответить
                              • > Чё так прогеры от фенсов батхертят? Простая же концепция.
                                ХЗ, может быть. Я до сих пор не могу сходу представить, где лево, а где право или всегда представляю карту, когда нужно узнать, в какой стороне находится запад, а в какой - восток. С этими фенсами та же ситуация. Есть операции с ордерингом acquire, release, relaxed, seq_cst. Какая операция и куда не пропускает чтение или запись? И почему они названы именно так, а не исходя из запрета на какой-либо reordering? А если операция - это инкремент, который включает в себя и чтение, и запись, то какой смысл имеет ордеринг? Вопросов получается больше, чем документация предоставляет.
                                Ответить
                                • Вот меня еще смущает фраза на cppreference о том, что aquire запрещает переставлять только ЧТЕНИЯ. Т.е. запись, если она не data dependent от чего-то читаемого под мутексом может выпрыгнуть и ВНЕЗАПНО выполниться до его взятия?!
                                  Ответить
                                  • Мьютексы отношения к фенсам не имеют. Просто названия схожие. А означают другое. Ну а как бы по теме говнокода автор много раз налажал и здесь тоже. Спинлок его говно и это судьба всех велосипедистов. Использовал неподходящие фенсы
                                    Ответить
                                    • Ну как это не имеют? Они же используются при реализации мутексов.
                                      Ответить
                                      • Кто тебе такое сказал? Я первым кину ему камень в спину.
                                        Ответить
                                        • Ну а что, на каком-нибудь ARM'е, одного CAS'а без фенса будет достаточно, чтобы захватить мутекс И гарантировать видимость всех нужных данных? И при отпускании аналогично?

                                          Или ты про std'шные фенсы, а не про процессорные?
                                          Ответить
                                          • говорю стд акьюр релиз не достаточно для реализации мьютекса. нужны фенсы сильнее
                                            Ответить
                                            • > говорю стд акьюр релиз не достаточно для реализации мьютекса. нужны фенсы сильнее
                                              Пруф в студию!
                                              Ответить
                                              • Какой тебе пруф? Ты даже сам до этого логически дошел, когда читал сипипиреференс
                                                Ответить
                                                • > сипипиреференс
                                                  Т.е. в Стандарте то же самое написано?
                                                  Ответить
                                        • В Qt сейчас полистал реализацию - для ARM'а напихали dmb (data memory barrier) во все щели кроме relaxed'ов. Ну там же не настолько идиоты работают, чтобы просто так это делать?
                                          Ответить
                          • > Every programmer
                            Даже пыхопыхник?
                            Ответить
                            • > > Every programmer
                              > Даже пыхопыхник?
                              Почему бы и нет? Может быть, это спасет его от дальнейшего развития как пыхопышника.
                              Ответить
        • >Не юзает
          _____________________________________________________________calculate_underscores_please;
          Ответить
    • Кстати, этот мутекс еще и нерекурсивный: второй вызов lock() из того же треда дедлокнется к хуям.

      Впрочем, это может быть и фичей.
      Ответить
    • А как насчёт перед фенсоватой операцией делать relaxed попытку? Или даже не атомарную?
      Ответить
      • В случае мутекса - нахуя? Если попытка удастся, тебе один хер фенс проходить, прежде чем к данным лезть. А если не удастся - там уже и пофиг, всё равно спать.

        В других случаях может и интересно будет. Вроде бы в double-checked синглтоне такую фишку с relaxed'ом и отдельным фенсом в успешной ветке юзали.
        Ответить
    • Проиграл со строчки в стандарте: They would be invalid for a hypothetical machine that is not tolerant of races or provides hardware race detection.

      Компьютер-расист.
      Ответить
      • Теперь на вопрос - "какого гуя твой код не работает?" я буду говорить - это потому что я нигер

        Внимание! Ваш код не будет скомпилен, так как вы не собрали весь хлопок
        Ответить

    Добавить комментарий