Магистерский курс C++ (МФТИ, 2022-2023). Лекция 21. Атомики, часть 3.

Поділитися
Вставка
  • Опубліковано 18 гру 2024

КОМЕНТАРІ • 18

  • @ИгорьЕрмаков-э3о

    Хотелось бы сделать несколько комментариев. Ссылки на стандарт ниже относятся к draft 4861 - это последний перед принятием официального C++20, который от него не должен отличаться.
    1) 14:15 Acquire и release операции даны в стандарте в терминах "sequenced before" и "synchronize with", и там нет упоминаний (я не нашёл?) по поводу особого отношения к операция чтения/записи. Потому, раз вы предпочитатете объяснять на переупорядочивании, то правильнее будет говорить, что acquire запрещает переупорядочивать до, а release - после, и операции чтения, и операции записи, а не отдельно acquire - чтения, а release - записи, как сказано на слайде.
    2) 16:45 Этот код содержит (внезапно!) неопределённое поведение. В соответствии с 16.5.4.11/1 нарушение предусловий функций стандартной библиотеки ведёт к неопределённому поведению. Теперь посмотрим на 31.8.1/6 и 31.8.1/12: для store параметр order не может быть memory_order::consume, memory_order::acquire или memory_order::acq_rel, а для load - memory_order::release или memory_order::acq_rel. Да, не все memory_order подходят для всех атомарных операций.
    3) 29:41 Нет, эти две модели памяти не для compare и swap, а для успешного и не успешного CAS соответственно. И это разные вещи. Ибо если следовать вашим словам, то вызовы compare_exchange_{weak,strong} герировали бы два барьера сразу. Более того, сходу я не могу придумать случая, когда требовался бы failure memory order более "жёсткий", чем success memory order. И в 31.8.1/23 указано, что при одном аргументе failure memory order будет идентичен переданному, за исключением acq_rel, который заменяется на acquire, и release, который заменяется на relaxed. Так что на ваш последующий вопрос в 1:12:40, когда вы говорите, что второй аргумент sequentially-consistent, и предлагаете указать до какой степени его можно ослабить, я отвечаю так: вы его уже ослабили до relaxed, и да, так можно (хотя весь остальной код я не проверял, потому можно только в том случае, если вы не ошиблись с success memory order).
    4) 32:23 Нет, этот пример вполне может вывести обе строчки: в данном примере у нас нет никакой гарантии, что мы прочитаем из x именно true. Семантика acquire-release гарантирует нам видимый порядок, но не гарантирует, что записанное с release будет мгновенно видно в другом потоке. В конце концов, представим, что весь код Thread 2 исполнился раньше (что бы это ни значило без sequentially-consistent гарантий :) ), чем код Thread 1: в этом случае мы можем увидеть оба вывода, потому что именно release synchronize with acquire, но никак не наоборот. Собственно, это написано в 6.9.2/6. Более того, в 48:15 вы показываете в целом аналогичный пример даже с более "жёстким" memory order, и совершенно верно говорите, что мы может увидеть два нуля. Может, вы это специально сделали, ибо перед этим вы говорите слушателям, что проблема не только в переупорядочивании команд, но и в "видимости" памяти разными потоками? Но в таком случае стоило бы вернуться к примеру с барьерами и сказать, что на самом деле там он тоже не помогает.
    5) 38:15 Я не думаю, что тут дело в нежелании процессора переупорядочивать. Вы ведь сами говорите, что на ARM используется LL/SC, а не просто CAS. И, конечно, это сделано признаком в кэш-линии, а у вас обе переменные рядом. Потому для начала я бы объявил и a, и b с alignas(std::hardware_destructive_interference_size). Не могу гарантировать, что сразу же получим wrong, но шансы гораздо выше.
    6) 41:19 Всё же volatile не работает :) Ну, и не надо учить людей, что якобы существует "UB которое никогда не проявится" - на эту тему есть замечательная исптория (www.linux.org.ru/forum/development/14422428). Но мало того, что у вас неопределённое поведение из-за data race, у вас ещё и барьеры не работают. В 31.11/2-4 ясно сказано, что fences работают только с ассоциированной атомарной операцией, коей в коде нет. Не могу сказать, что это ведёт к неопределённому поведению, но к отсутствию барьера вполне может привести - на всё воля компилятора.
    7) 42:31 Не понимаю, почему вы не показываете слушателям правильный код без data race. Мои пробы на godbolt дают такой же код, что и показанный вами. Всё же считаю, что показывать студентам код с неопределённым поведением следут только с пояснением, что так ни в коем случае делать нельзя, а не как рабочий вариант.
    8) 1:10:25 Так и не понял для чего на делать store в теле конструктора, если конструкторы std::atomic вообще не являются атомарными операциями, и потому могут быть "переупорядочены" как угодно. Правда, в 31.8.1/3 нас предупреждают о возможной гонке с конструктором, но это не наш случай, ведь очевидно, что в другой поток должен передаваться указатель (ссылка) на уже сконструированную очередь, т.е. всё должно происходить с соответствующими барьерами.
    9) Не думаю, что выравниванием std::vector что-то даёт в данном случае. Я бы предложил выровнять CellTy по кэш-линии: всё же там атомарный счётчик, нам ни к чему лишние синхронизации с другими ядрами (или даже конфликты LL/SC на ARM). Но, конечно, это зависит от размера T.

  • @MultiGeho
    @MultiGeho Рік тому +3

    Блин, такой классный голос, подача и темы отличные! Если бы была нейронка, которая генерила мне лекции Константина Владимирова на произвольные темы, я бы согласился впасть в матрицу, чтобы пару месяцев только жрать, спать и смотреть лекции Константина Владимирова

  • @pavelrid
    @pavelrid Рік тому

    Спасибо! Получился замечательный цикл про Атомики!

  • @nneekkiitt
    @nneekkiitt 8 місяців тому

    Не совсем понял пример на 33:00. Почитал про atomic_thread_fence на cppreference, и там написано, что atomic_thread_fence с release запрещает всем операциям чтения/записи, написанным до барьера, переноситься дальше вызовов store после барьера. Acquire, как я понял, работает противоположно, т.е. запрещает операциям чтения/записи, написанным после барьера переноситься раньше вызовов load, написанных до барьера.
    На слайде же в Thread 1 под барьером нет вызовов store, а в Thread 2 над барьером нет вызовов load. Т.е., получается, барьеры вообще не влияют на порядок действий в данной ситуации?

  • @vladalu9794
    @vladalu9794 Рік тому +1

    У Федора Пикуса есть отличная книга "The Art of Writing Efficient Programs" где рассмотрены модели памяти подробно, интересная книжка. Спасибо большое за интересное видео.

  • @e47ca0ef
    @e47ca0ef Рік тому +1

    1:20:20 Возможно, 20%-ый оверхед на любом количестве потоков возникает из-за того, что sleep(1ms) на самом деле делает паузу не ровно 1ms, а 1.2ms из-за дискретности таймера?

    • @tilir
      @tilir  Рік тому

      Блестящая догадка!

  • @РоманМитин-п4т
    @РоманМитин-п4т Рік тому

    На 33:00 всё ещё можем увидеть две строчки несмотря на барьеры, если оба потока сначала сделают store, а затем пойдут загружать значение

    • @tilir
      @tilir  Рік тому

      Если оба успели сделать store, выводить строчку не пойдёт ни один. Они же читают под антипредикатом. Барьеры тут гарантируют, что успели.

  • @РоманМитин-п4т
    @РоманМитин-п4т Рік тому +1

    Есть вопрос, касающийся memory_order. Какова может быть причина того что memory order это не шаблоный параметр ароматных функций? Разве можно представить разумную программу, которая в зависимости от рантайма будет менять memory order у атомарных операций и при этом будет работать корректно? И что вообще компилятор будет делать с таким кодом, если там memory order не известен на этапе компиляции, воткнёт switch или просто всегда будет ставить seq_cst?

    • @tilir
      @tilir  Рік тому +1

      Это очень разумный и удивительно глубокий вопрос который как-то мимо меня проскочил. Я думаю (впрочем не уверен), что тут правильный ответ это композиция. Вы можете написать свою функцию которая принимает memory order и спокойно передать его в стандартную функцию. Если бы вам нужно было выводить для этого шаблонный параметр, вы бы были вынужденно соглашаться на расползание шаблонного параметра по вашему коду. Что может быть не опцией, если функция, например, виртуальная.

  • @andrewzabavnikov6316
    @andrewzabavnikov6316 Рік тому

    Пример про seq_cst точно такой же как для вывода надписей "x not y" до этого, но говорятся про них разные вещи

    • @andrewzabavnikov6316
      @andrewzabavnikov6316 Рік тому

      Возможно, чего-то не понял, но хотелось бы прояснения этого момента, если возможно

  • @neon1ks
    @neon1ks Рік тому

    На 61 минуте лекции говорится про сайт, можно ссылку на него?

    • @tilir
      @tilir  Рік тому +1

      www.1024cores.net/

  • @alex_s_ciframi
    @alex_s_ciframi Рік тому

    Константин, спасибо за лекцию. Понравилась мнемоника, я сам никак не мог запомнить.
    Предлагаю расширенную дурацкую мнемонику:
    "Релиз - не пиши вниз,
    Эквая - читай не взлетая"
    :D

  • @ViacheslavMezentsev
    @ViacheslavMezentsev Рік тому +2

    После всего этого многопоточного C++ я начинаю завидовать программистам ПЛК.

    • @pavelrid
      @pavelrid Рік тому

      Скукота, а в C++ всегда весело и интересно и это гарантируется самим языком!