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

    0

    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
    16. 16
    17. 17
    18. 18
    19. 19
    20. 20
    21. 21
    22. 22
    23. 23
    24. 24
    25. 25
    26. 26
    27. 27
    28. 28
    29. 29
    30. 30
    31. 31
    32. 32
    33. 33
    34. 34
    35. 35
    36. 36
    37. 37
    38. 38
    39. 39
    40. 40
    41. 41
    42. 42
    43. 43
    44. 44
    45. 45
    46. 46
    47. 47
    48. 48
    49. 49
    50. 50
    51. 51
    52. 52
    53. 53
    54. 54
    55. 55
    56. 56
    57. 57
    58. 58
    59. 59
    60. 60
    61. 61
    62. 62
    63. 63
    64. 64
    65. 65
    #include <cstdlib>
    #include <chrono>
    #include <iostream>
    #include <thread>
    
    int p = 0;
    int *q = nullptr;
    
    void g()
    {
        using namespace std::chrono_literals;
    
        std::cout << "g()" << std::endl;
    
        std::cout << "g(): p = 1" << std::endl;
        p = 1;
    
        std::this_thread::sleep_for(1s);
    
        
        if (q != nullptr) {
            std::cout << "g(): *q = 1" << std::endl;
            *q = 1;
        } else {
            std::cout << "g(): q == nullptr" << std::endl;
        }
    }
    
    void f()
    {
        using namespace std::chrono_literals;
    
        std::cout << "f()" << std::endl;
    
        if (p == 0) {
            std::cout << "f(): first loop start" << std::endl;
            while (p == 0) { }  // Потенциально конечный
            std::cout << "f(): first loop end" << std::endl;
        }
    
        int i = 0;
        q = &i;
        std::cout << "f(): second loop start" << std::endl;
        while (i == 0) { }  // Потенциально конечный, хотя в условии только автоматическая пельменная
        std::cout << "f(): second loop end" << std::endl;
    }
    
    int main()
    {
        using namespace std::chrono_literals;
    
        std::cout << "f() thread start" << std::endl;
        auto thr1 = std::thread(f);
        thr1.detach();
        std::this_thread::sleep_for(1s);
    
        std::cout << "g() thread start" << std::endl;
        auto thr2 = std::thread(g);
        thr2.detach();
        std::this_thread::sleep_for(2s);
    
        std::cout << "Done" << std::endl;
        
        std::_Exit(EXIT_SUCCESS);
    }

    Ожидание:

    f() thread start
    f()
    f(): first loop start
    g() thread start
    g()
    g(): p = 1
    f(): first loop end
    f(): second loop start
    g(): *q = 1
    f(): second loop end
    Done

    Запостил: gost, 28 Сентября 2020

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

    • Реальность:
      f() thread start
      f()
      f(): first loop start
      g() thread start
      g()
      g(): p = 1
      g(): q == nullptr
      Done

      Реальный пример: https://ideone.com/OCJQg9 («Wandbox» лежит, зараза).
      А чтобы получить ожидаемый вывод — надо конпелировать с «-O0».

      Выхлоп компилятора прекрасен (чуть упрощённый кот): https://gcc.godbolt.org/z/Kx4xeE
      f():
              mov     eax, DWORD PTR p[rip]
              test    eax, eax
              je      .L5
              lea     rax, [rsp-4]
              mov     QWORD PTR q[rip], rax
      .L6:
              jmp     .L6
      .L5:
              jmp     .L5
      Ответить
    • Да, кстати, согласно Борманду этот код — UB (https://govnokod.ru/26981#comment579180).
      1.10 Multi-threaded executions and data races

      The implementation may assume that any thread will eventually do one of the following:
      — terminate,
      — make a call to a library I/O function,
      — access or modify a volatile object, or
      — perform a synchronization operation or an atomic operation.

      В f() нет ни I/O, ни доступа к волатильным объектам, ни атомарных операций.

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

        Это на x86 мы привыкли к полной когерентности, а есть ведь платформы с более слабой моделью памяти, где проц тупо не будет перезагружать кешлайн, в котором лежит p или i. Ты ведь не дал ему такого повода.
        Ответить
      • Странно что это вызывает удивление.
        Ответить
    • q и p нужно объявлять как volatile? Или i?
      Ответить
      • Нет. Как атомик. volatile к тредам не имеет вообще никакого отношения, он для прерываний и железа.
        Ответить
        • Как всё сложно...
          Ответить
          • Именно поэтому я просто юзаю мутексы и теку. В них все нужные барьеры и гарантии встроены.
            Ответить
        • volatile ничего же не гарантирует в любом случае?
          Ответить
          • volatile гарантирует, что конпелятор не будет выёбываться, переставляя и выбрасывая записи и чтения. В общем-то и всё.

            Это важно для обращения ко всяким железкам, когда порядок записей и даже чтений имеет значение.
            Ну и в обработчике прерываний, если у тебя с ним какие-то переменные расшарены.
            Ответить
            • показать все, что скрытоvanished
              Ответить
              • volatile предотвращает OoO (реордеринг) на одном ядре. И не даёт компилеру полностью выкидывать такие циклы.

                Every access (read or write operation, member function call, etc.) made through a glvalue expression of volatile-qualified type is treated as a visible side-effect for the purposes of optimization (that is, within a single thread of execution, volatile accesses cannot be optimized out or reordered with another visible side effect that is sequenced-before or sequenced-after the volatile access. This makes volatile objects suitable for communication with a signal handler, but not with another thread of execution

                Опять же, я считаю это не совсем правдой. volatile можно использовать для синхронизации если старая-добрая одноядерная машина.

                >у нас в жабе всё проще
                Я бы не сказал. Оно везде сложно.
                Они в 8ой вроде fence из крестов навезли.
                Ответить
                • показать все, что скрытоvanished
                  Ответить
                  • В крестах тоже есть happens before. Через него там вся семантика и описана.
                    Ответить
                    • >В крестах тоже есть happens before

                      Они из java memory model его и заимствовали :)

                      Но в лучших традициях С++, они поступили согласно принципу: давайте возьмём концепт и сделаем его в 10 раз сложнее.

                      Просто в Йажа volatile был full fence. Крестобляди рассудили что это черезчур медленно и черезчур просто и добавили 4 разных memory_order.
                      Ответить
                      • показать все, что скрытоvanished
                        Ответить
                        • >Хепнс бефор куда более внятная и простая конструкция

                          Но она хуёво ложится на разнообразие железа и низкоуровневых инструкций. (MFENCE, LFENCE, SFENCE)

                          И не всегда имеет оптимальный пирформанс, накладывая своими гарантиями лишние ограничения на компилятор.

                          Во многих случаях достаточно memory_order_relaxed.

                          Именно поэтому я за «C++».
                          Ответить
                          • >И не всегда имеет оптимальный пирформанс,
                            угу)

                            ну это же вечный трейдофф: "много думать" versus тормоза.
                            Вот в питоне люди юзают GIL, и им куда проще жить
                            Ответить
                    • ну волатайл-то появилась намного раньше;)

                      когда вообещ в кресты завезли мемори модел для тредов? C++11?
                      Ответить
                • > volatile можно использовать для синхронизации
                  > если старая добрая одноядерная машина

                  Нет. Разве что в примитивных случаях, где relaxed хватает. Насколько помню, volatile не является конпеляторным барьером и не запрещает переупорядочивать обычные записи вокруг себя. Только другие volatile и сайд-эффекты. Т.е. тот же мутекс из волатайла получится весьма хуёвый.
                  Ответить
                  • > Т.е. тот же мутекс из волатайла получится весьма хуёвый.
                    Да.

                    > Разве что в примитивных случаях, где relaxed хватает.
                    Да. Именно тот пример, который я привёл. Когда переменная меняется один раз за всё время работы программы.

                    Даже пытаться использовать его как атомик — путь в ад.
                    Ответить
                  • приведи пример, когда на одноядерной машине volatile нельзя юзать как мутекс

                    >не является конпеляторным барьером
                    запись в волатилку всегда имеет сайд эффект, а значит код ПОСЛЕ него должен быть реально исполнен после этой записи, не?
                    Ответить
                    • Нет. Только другие сайд-эффекты и volatile обращения. Остальной код конпелятор волен переставлять как ему заблагорассудится.
                      Ответить
                      • показать все, что скрытоvanished
                        Ответить
                        • int data;
                          volatile int mutex;
                          
                          // было
                          data = 42;
                          lock = 0; // release mutex
                          
                          // стало
                          lock = 0;
                          data = 42;
                          Второй тред увидит отпущенную лочку, пойдёт читать данные и наебнётся. Если же вместо volatile взять атомик с release семантикой, то такой хуйни не будет.
                          Ответить
                          • показать все, что скрытоvanished
                            Ответить
                            • Я написал за volatile, как гипотетическую возможность, более дешевого способа чтения из пельменной в одной специфичной ситуации.

                              Любому вменяемому человеку понятно что использование volatile в качестве примитива синхронизации — полная хуйня.

                              Особенно в свете выхода С++11 и появления широкого набора кроссплатформенных альтернатив.
                              Ответить
                              • показать все, что скрытоvanished
                                Ответить
                                • Да. Вроде даже на арме можно на этот эффект посмотреть.

                                  Спасибо что напомнил, я когда-то хотел попробовать, но у меня не было подходящего железа.
                                  Ответить
                                  • а в x86 писание в память упорядоченное, так что он гарантирует тебе выпёздывание буфера в кеш в том порядке, в каком его заполнили? или почему обязательно нужен арм?
                                    Ответить
                                    • Да, в арме не было единого глобального порядка записей, который наблюдают все процы. Теперь походу есть. И для эксперимента подойдёт только какой-нибудь power pc.
                                      Ответить
                                      • А в каком поколении армов ситуация изменилась?
                                        Ответить
                                        • Стеоверфлоу пишет что гарантию добавили в ARMv8
                                          Ответить
                                          • Т. е. в ARMv7, которых пока ещё дофига в обращении, гарантии нет?
                                            Ответить
                                      • Самый мощный проц это Alpha, царство ему небесное
                                        Вика пишет, что Dependent loads can be reordered, хотя мне это не очень понятно. Видимо бывают сорта депенденсов.

                                        А что у ваших армов с кококококогерентностью кеша? попадание в кеш-то гарантировано бродкастится во все кеши, или тоже нужно явно?
                                        Ответить
                                • Зависит от языка на котором это написано, платформы и фазы Луны.
                                  Ответить
                                  • мы про сишечку

                                    В джаве вроде бы нет, потому volatile сделает fence.
                                    Ответить
                                • показать все, что скрытоvanished
                                  Ответить
        • Подтверждаю. Если сделать вот так:
          #include <atomic>
          
          std::atomic<int> p = 0;
          
          void f() {
               if (p == 0) {
                  while (p == 0) { /* ... */ }  // Потенциально конечный
              }
          }

          То получится вот это:
          f():
          .L6:
                  mov     eax, DWORD PTR p[rip]
                  test    eax, eax
                  je      .L6
                  ret
          p:
                  .zero   4
          Ответить
          • Забавно, что с volatile для x86 ты получишь точно такой же код. А потом на какой-нибудь альфе он у тебя повиснет, в отличие от кода с std::atomic.
            Ответить
            • >>> А, ну да, это ж кресты, тут думать надо.[/color]
              Ответить
              • Над этим нужно думать не только в крестах. Разве что в крестах нужно думать в разы сильнее.

                Рекомендую почитать JCP, там все эти вопросы очень подробно разобраны.
                Ответить
            • хех, хотел спросить, есть ли в крестах compare and exchange

              оказалось, что это тот же неймспейс

              https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange
              Ответить
        • > volatile к тредам не имеет вообще никакого отношения
          Я уже вроде приводил контр-пример.

          volatile boolean done=0;
          ...
          while (!done)
          Ответить
          • И что этот контр-пример опровергает?

            То, что на интеле всё когерентно и в атомик риде нет барьеров и специальных инструкций, поэтому и обычное чтение сойдёт?
            Ответить
            • Что volatile можно использовать в многопоточных программах на флаге завершения.

              >То, что на интеле всё когерентно
              А на других платформах разве будут проблемы?
              Ответить
              • На x86 - вай нот. В общем случае - нет. Атомарное чтение может содержать какие-то специальные инструкции, которых нету в volatile чтении.
                Ответить
                • Так а какие спец. инструкции?

                  У нас есть признак шатдауна. Он может поменяться только одним способом (из 0 в 1).

                  Как только он стал ненулевым рано или поздно все это заметят и остановятся.
                  Ответить
                  • Ну смотри, даже на интеле у тебя запись может застрять во write-back кеше и годами висеть в нём, не попадая в оперативку. Но за счёт протокола когерентности ядро с циклом об этой записи узнает.

                    Если у тебя там какая-то хуйня с тыщей ядер, то им будет очень дорого следить за кешами друг-друга. В лучшем случае они будут мониторить только реальные записи в память (т.е. тебе понадобится write-through семантика на done = 1). В худшем случае они вообще ничего не будут мониторить (и тогда нужно чтение с инвалидацией кеша на !done). volatile ничего из этого не даёт.
                    Ответить
                    • Ну когда-то оно ведь остановится.

                      Я к тому что атомик-чтения и особенно мьютексы были бы слишком дорогими в этой ситуации с флагом.

                      volatile раньше был вполне адекватным методом.

                      Но в целом после завоза в кресты std::memory_order и happens-before семантики оно бесполезно.
                      Ответить
                      • > когда-то оно ведь остановится

                        Когда юзер психанёт и ткнёт в резет, ага. Представь, что это были ядерные треды, которые никто никогда не вытеснит.

                        > слишком дорогими

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

                          Я вижу только одну ситуацию, что они не увидят друг-друга: в какой-то NUMA машине из нескольких сокетов. Но в таких обычно стараются делать NUMA-aware пулы чтобы потоки взаимодействовали в рамках своего сокета.

                          >атомарное чтение уже включено в цену (интел, арм)
                          Разве? Там же лишний LFENCE будет на каждом чтении, не?
                          Ответить
                          • > lfence

                            Зачем? Зачем? Посмотри на годболте во что атомарное чтение раскрывается.

                            На арме вроде тоже чтение бесплатно, а вот в записи барьер. Но я не изучал их модель.
                            Ответить
                            • > Посмотри на годболте во что атомарное чтение раскрывается.
                              Вон, я выше по ветке привёл реальный пример. В «mov eax, DWORD PTR p[rip]» раскрывается.
                              Ответить
                            • >while (p == 0)

                              В ваших крестах с блядскими перегрузочками нихера не понятно. Какие именно гарантии у этого чтения?
                              Ответить
                              • sequentially consistent судя по выхлопу для ARM.

                                Пиши явно: while (!p.load(std::memory_order_relaxed)), тогда бесплатно. Для флажка об остановке релакса должно хватить.
                                Ответить
                    • >Если у тебя там какая-то хуйня с тыщей ядер, то им будет очень дорого следить за кешами друг-друга.

                      Так а они ведь как-то видят изменения volatile-переменных после прерываний.
                      Ответить
                      • > после прерываний

                        Прерывание происходит на том же самом ядре, со своим собственным кешем проблем не будет.

                        А для MMIO с железками как правило write back отключен, поэтому проц всё честно пишет.
                        Ответить
    • Ничего не понимаю... И это программисты? Говно какое-то... Пидоры, блядь. Родина им дала практические задачи! Решай, решай задачи из предметной области — блядь, не хочу, хочу жрать говно! Что такое? Это программирование?! Это программирование?! Суки... Ядер понакупили, заборов и мьютексов понаставили! Говно жрут! Пидоры, блядь, ёбаные...
      Ответить
    • Прочитал "конченый"
      Ответить

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