- Ваша сильная сторона?

- Multithreading

- Вот 3 задачки. Сможете сделать до завтра? 

- Я не могу решать столько задач одновременно

Согласитесь, сoncurrency — одна из непростых тем программирования. В начале своей карьеры программиста кто‑то всячески пытается избежать погружения в эту тему, но рано или поздно приходится столкнуться с concurrency проблемами. Это может случиться, потому что нужно написать потокобезопасный код или прилетел баг на уже имеющийся код.

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

Статья не про серебряную пулю, как исправить concurrency проблемы, а про костыльные подходы веселых и находчивых, которые не решают суть проблемы.

Sleep – вместо тысячи слов

Дело в том, что Sleep добавляют не только начинающие программисты. Мне встречались программисты, которые при опыте 3–5 лет работы делали такой фикс и в тестах кода, и в продакшене. Подобный код является по своей сути бомбой замедленного действия. Важно понять одно: если вы делаете фикc такого рода, он может быть замаскирован до поры до времени. Потом пройдёт обновление операционной системы или же вы поменяете железо на новое и все, тайминги изменятся и старые проблемы станут явными.

Обратите внимание, увеличение времени sleep‑а — тоже не выход :)

Подход «Логов, больше логов» маскирует concurrency проблему

Порой обнаружение корня проблемы занимает много времени, поэтому программисты на практике часто добавляют логи для дальнейшего изучения проблемы, что правильно. Можно добавить немного логов — это действительно полезно и помогает решению проблемы. Но, бывает, добавляют лог‑сообщения через строчку. У вас там маленький кусок кода, обложенный кучей логов, и код чудесным образом перестает падать. Потом через какое‑то время поднимается история бага, и оказывается, что он не воспроизводился некоторое время. Поэтому баг закрывают по причине «Не воспроизводится N месяцев, возможно, уже пофиксили». А на деле баг остался.

Давайте разберемся, почему это пока «работает». В местах добавления логов код начинает выполняться медленнее и создаёт иллюзию, что рассинхрона нет и все «заработало», но ничего подобного. Нужно всегда помнить, input/output операции дорого стоят с точки зрения ресурсов системы.

Try‑catch как фикс

Оборачивать все в try‑catch конструкции вместо «нормального» фикса — тоже не выход. Осознанное использование этого подхода в продакшене я видела лишь однажды.

Дело было так. Жил‑был непотокобезопасный код много лет, который использовался в разных частях проекта. Время от времени появлялись креш‑дампы из‑за отсутствия средств синхронизации потоков в коде, который многие использовали как потокобезопасный. Со временем код стал устаревать, его начали заменять в проектах, и получил этот непотокобезопасный код статус «устаревший» и кандидат на удаление. И вот код уже встречается только в одном месте, креш‑дампы естественно продолжают появляться. Тикеты на проблему накапливаются, и менеджеры просят пофиксить проблему с условием минимальных затрат по времени. Переписывать тысячи строк кода, чтобы синхронизовать устаревший код, который удалят через 2–3 месяца, бессмысленно, только время потратишь. Решением категории «дёшево и сердито» оказывается обернуть соответствующие методы в try‑catch. Все понимают, что это корявое решение проблемы, но ничего не поделаешь, если желательно исправить сегодня. По факту, это даже не фикс, а маскирующий костыль. Делать подобное в поддерживаемом коде нельзя.

Давайте повторим N раз до победного

Бывает, какая‑то часть боевого кода или тест периодически падает. Быстро найти проблему не удается, да и проблема не совсем очевидная. Тут приходит гениальная мысль обернуть код в try‑catch, но не просто ловить исключения, а повторять падающий код несколько раз до победного конца. Такой подход напоминает русскую рулетку: выстрелит — не выстрелит. Результат выполнения кода вроде как улучается: количество падений уменьшается. Такой подход дает только временный эффект. Ведь реальная проблема не решена, и в какой‑то момент частота воспроизведения проблемы снова возрастет, и вы возвращаетесь к тому, с чего начали.

Как ни крути, при разных подходах маскировки concurrency проблемы, ее все равно придется решать.

Комментарии (59)


  1. IkaR49
    03.06.2023 15:23
    +7

    Когда я был ещё молод и зелен достался мне в наследство код, где в главном ui потоке выполнялись синхронные операции с очень большими файлами, которые сильно фризили gui приложения. Тогда я как следует разобрался с многопоточностью, вынес работу с ФС в фоновый поток и сделал достаточно неплохой обмен сообщениями между потоками. Время шло, код работал (и работает до сих), но изменился способ обработки файлов, да и сами файлы: их стало много больше, но сами они стали меньше по размеру. И сейчас получается, что gui снова фризится, но причина теперь в том, что сообщений между потоками перебрасывается просто бешенное количество. При этом, gui "единомоментно" фризится на совсем чуть-чуть, но таких фризов очень много. Попытка вернуть обработку обратно в один поток показала, что общее время выполнения всех операций становится значительно меньше, но превращается в "один гигантский фриз".

    В принципе, как решить проблему мне ясно (пакетирование, немного иначе обмениваться сообщениями, третий поток с подготовкой данных сразу для отображения в ui), но как-то всё руки не доходят.

    Скоро передам эту задачу студенту, посмотрим, что предложит :)


    1. mvv-rus
      03.06.2023 15:23
      +4

      То, что он вообще может предложить, сильно зависит от среды выполнения: ОС, языка прогрограммирования, архитектуры GUI...

      А варьироваться это "может предложить" от "элементарно" (например, асинхронный запуск IO в главном UI-потоке с проверкой, когда надо, завершения IO в этом потоке или же отправкой события завершения в цикл обработки событий UI, если UI имеет событийную архитектуру; вся эта благодать доступна, например, в Windows NT и ее потомках в виде современных версий Windows) до "невозможно" (это тоже было в Windows, но той, которая не NT, и там приходилось в такие моменты созерцать песочные часы вместо курсора).

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


      1. IkaR49
        03.06.2023 15:23
        -1

        Вы знаете много студентов 3 курса, которые знают что такое async i/o и умеют с ним работать? Я пока что ни одного) Да что там, я знаю достаточно много состоявшихся программистов, которые не умеют писать обычный thread-safe код (о чем, собственно, и была данная статья, в комментариях к которой мы находимся)


        1. nin-jin
          03.06.2023 15:23

          Дайте им в руки thread-safe язык программирования и они сразу научатся.


          1. Tangeman
            03.06.2023 15:23
            +2

            Это какой такой язык thread-safe? Какой из существующих компиляторов сам, без помощи разработчика, определяет какие участки кода и области памяти нужно защищать а какие нет, да ещё и с учётом того что ситуация меняется в процессе выполнения в зависимости от того какие данные поступили на вход?

            В теории конечно можно представить себе компилятор который на всякий случай будет пытаться синнхронизироватиься на уровне операторов или функций, "тайно" вставляя примитивы синхронизации везде где можно (и заодно где не нужно), можно даже (наверное) полностью избавиться от мутаций - но за это придётся заплатить высокую цену в плане производительности и/или повышенных требований к памяти.

            Можно пойти чуть дальше и с помощью статического и даже динамического анализа попытаться найти участки кода которые реально требуют синхронизации, но тут просто сесть в лужу не зная намерений разработчика, т.е. какие-то подсказки в виде прагм или модификаторов типов или переменных всё равно понадобятся, и всё равно принципиально это ситуацию не улучшит (скорее даже наоборот).

            Может, если речь о чём-то очень прямолинейном на уровне "Hello, world!" и можно сделать всё почти автоматически, но на любом сложном проекте вся эта автоматика либо сломается, либо замедлит всё в разы.

            Впрочем, даже это всё (если и будет когда-то реализовано) не спасёт от потенциальных dead-lock которые в принципе невозможно обойти не зная что должен делать код, т.е. не читая и не понимая мыслей разработчика или архитектора - но в ближайшие 20 лет это маловероятно.

            На данном этапе пока всё ещё проще делать синхронизацию почти вручную (используя примитивы синхронизации и прочие подобные механизмы) и пытаться "думать как процессор", но это всё же чуток сложнее чем "распознавание лиц в 10 строк на питоне".


            1. nin-jin
              03.06.2023 15:23

              Например, в D все мутабельные данные по умолчанию локальны для потока и нужно хорошенько поплясать вокруг типов, чтобы убедить компилятор, что их действительно можно безопасно передавать в другой поток. Например, передавать можно лишь иммутабельные, перемещаемые, копируемые или синхронизированные структуры.


        1. mvv-rus
          03.06.2023 15:23

          У меня вообще сейчас нет знакомых студентов 3 курса. А по себе я судить не могу - когда я на 3 курсе учился, у меня доступ к ЭВМ был только через перфокарты - так давно это было. Впрочем, асинхронный ввод-вывод имелся и тогда - но об этом я узнал немного позже.

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


    1. nronnie
      03.06.2023 15:23

      Работу с ФС следует не выносить в фоновый поток, а реализовывать через асинхронные вызовы. Но я не знаю что у вас была за платформа и АПИ, поэтому мне сложно судить.


      1. IkaR49
        03.06.2023 15:23
        +1

        У нас в конторе своё кроссплатформенное SDK почти на все случаи жизни, которое появилось ещё в середине 90-х. Чтобы использовать что-то другое, тот же boost-asio, надо прям очень сильно поспорить с начальником и аргументировать необходимость внедрения этого чего-то стороннего. Собственно, в те времена я не умел спорить с начальником) Да и сейчас не уверен, что стоит, ибо:

        1. Это очень маленькая тулзина для внутренних нужд;

        2. Там сейчас под капотом libarchive, а он не умеет в асинхронные операции.

          Ну и последнее: ввиду старости нашего SDK у нас практически никто не умеет в асинхронщину. Даже коды работы с сетью в нас имеют синхронное API.


        1. nronnie
          03.06.2023 15:23

          У нас в конторе своё кроссплатформенное SDK

          Ой-вэй. Когда мне на интервью говорят что-нибудь типа: "Для разработки мы используем свой собственный фреймворк (ORM, DI контейнер, MQ, RDBMS, etc - нужное подчеркнуть).", то у меня в голове сразу начинает звучать сигнал тревоги :)


          1. IkaR49
            03.06.2023 15:23
            +2

            Ага, я тоже плююсь. Поэтому в пет-проектах я активно использую что-то более популярное, чтобы быть в тонусе :)


        1. mayorovp
          03.06.2023 15:23

          Если у вас ещё и старый стандарт плюсов — то могу посоветовать только бежать подальше.


          А на новом стандарте можно написать свой примитив "асинхронная операция, выполняющаяся в UI потоке", после чего всё значительно упростится.


        1. nin-jin
          03.06.2023 15:23
          -1

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


      1. equeim
        03.06.2023 15:23

        Асинхронные вызовы точно также приведут к обмену сообщениями между потоками (если команды на чтение файлов приходят из ui треда) - просто это будет происходить "под капотом". Никакой магии в них нет.


        1. mayorovp
          03.06.2023 15:23

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


          А магии там и правда нет.


          1. equeim
            03.06.2023 15:23
            +1

            Согласен, я имел в виду что современные асинхронные подходы сами по себе не решают подобных проблем с производительностью. Если ты запускаешь 10 тысяч мелких асинхронных операций из ui треда которые потом возвращаются в обратно в ui тред то не имеет значения какой подход используется - самописанные очереди сообщений, async/await, джавовские экзекуторы - это приведет к тем же проблемам.


      1. PsyHaSTe
        03.06.2023 15:23

        Насколько мне известно единственный нормальный файловый асинк есть только в винде. В линксе вроде худо-бедно запилили io_uring, но его поддержка ещё все же хромает во многих языках и платформах.


        1. mayorovp
          03.06.2023 15:23

          Учитывая, что они там упирались в количество сообщений — асинхронный файловый ввод-вывод и не нужен, всё равно отдельный поток заводить придётся.


    1. PsyHaSTe
      03.06.2023 15:23

      Попытка вернуть обработку обратно в один поток показала, что общее время выполнения всех операций становится значительно меньше, но превращается в "один гигантский фриз".

      Достаточно было просто сократить количество обменов сообщений, не посылая их по одному а пачками скажем по 100 или 1000. Менять дизайн системы практически не нужно, просто вставить батчинг в 1 месте и цикл в другом, общее количество изменений — десяток строк.


  1. nikhotmsk
    03.06.2023 15:23
    +1

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


    1. miga
      03.06.2023 15:23
      +7

      «Хорошо» или «плохо» выясняется не с помощью присказок и тайного знания, а путем бенчмарков и профайлинга


    1. cdriper
      03.06.2023 15:23
      -3

      только в такую очередь сообщение без мютекса никак не добавишь )


      1. EvilBlueBeaver
        03.06.2023 15:23

        Lock-free queue


        1. cdriper
          03.06.2023 15:23
          -4

          в контексте обсуждаемого вопроса это одно и тоже


          1. EvilBlueBeaver
            03.06.2023 15:23
            +3

            Я не знаю, что вы подразумеваете под очередями сообщений, но все известные мне и широко используемые очереди сообщений, которые используются для общения именно между «тредаии», мьютексов не используют, насколько я знаю. Я про эрланговые мейлбоксы, растовые mpsc и гошные каналы. Ну и «самопальные» очереди в тех случаях, где оно не встроено, тоже пилят на всяких атомиках и прочем обычно.


            1. cdriper
              03.06.2023 15:23
              -7

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

              а два -- когда я говорю "одно и тоже" я имею ввиду, что мы говорим о примитиве исключительного доступа и тут не важно, как он фактически реализован


              1. EvilBlueBeaver
                03.06.2023 15:23
                +2

                Все эти очереди потокобезопасны и грин треды тут вообще ни при чем. Тем более что зачастую грин треды вполне себе на обычных потоках делаются (шедулеры в эрланге или какой нибудь тредпул в токио в расте).

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

                Атомики и мьютексы вообще несравнимые вещи и то как они реализованы очень сильное значение имеет.


                1. cdriper
                  03.06.2023 15:23

                  смысл в том, что в реальных сценариях все эти lock-free подходы (даже с учетом прорывного открытия disruptor pattern в 11-м году) обычно работают с производительностью, сравнимой с прямолинейным кодом на основе мютекса (и кроме lock-free в названии еще и содержать спинлоки в коде)

                  https://youtu.be/_qaKkHuHYE0


                  1. EvilBlueBeaver
                    03.06.2023 15:23

                    Так я нигде и не утверждал, что они быстрее мьютексов. Более того я прекрасно понимаю, что оно в негативном сценарии в отличие от мьютекса еще и проц будет жрать как не в себя. Разница только в том, что в одном случае все будет намертво висеть, если ресурс занят, а в другом случае возможны варианты.
                    Реальный сценарий реальному сценарию рознь. Если втупую взять какую-то общую структуру, которая шарится на много потоков и поменять мьютексы на lock-free, то почти наверняка оно хуже и будет. Все системы с обменом сообщениями, с которыми я работал и работаю подразумевают, что таких глобальных данных под мьютексом, в которые ломятся все кому не лень, нужно избегать и заменять на отдельные сообщения между отдельными потоками. В некоторых случаях у вас даже возможности такой не будет пошарить данные, например в эрланге(ну формально, конечно, можно и там извратиться, но это надо прям очень сильно захотеть). Но благодаря этому многопоточная разработка становится в разы проще и понятнее и помогает избежать кучи проблем.
                    Об этом (как мне показалось) и было оригинальное сообщение в треде. Ну и да, даже очереди с мьютексами (а то и вообще с RWLock) под капотом с точки зрения отсутствия возможности отстрелить себе ногу в разы лучше, чем голые мьютексы. Да, оно будет очевидно медленнее, но избавит от кучи головной боли, о которой в частности и написана статья.

                    По поводу видео, его я посмотреть не успел, но заголовок говорит о том, что там описывается mpmc, мои примеры выше работают либо в варианте mpsc, либо в варианте spsc вообще.


                  1. nin-jin
                    03.06.2023 15:23

                    go.d - 2015 год. И в отличие от disruptor обходится совсем без CAS за счёт round-robin по ring-buffers.


              1. shai_hulud
                03.06.2023 15:23

                lock-free queue не подразумевает исключительного доступа. Lock-free структуры данных вообще не связаны с примитивами синхронизации.


                1. nin-jin
                  03.06.2023 15:23
                  -3

                  Предлагаю подумать над парой фактов:

                  • Мьютекс может быть реализован через lock-free spin-lock.

                  • live-lock может заблокоровать ваш поток на неограниченный срок.

                  А потом глянуть этот материал:


                1. cdriper
                  03.06.2023 15:23
                  +1

                  универсальная lock-free queue с поддержкой multi-producer и multi-consumer, без спинлоков и с (хотя бы) 2x производительностью от примитивной версии на мютексе это что-то из фантазий на тему вечного двигателя


          1. nin-jin
            03.06.2023 15:23

            В данном контексте уместнее wait-free queue. Легко реализуется через циклический буфер. Работа парного треда при этом вообще никак не влияет на твой.


        1. nronnie
          03.06.2023 15:23

          Lock-free queue

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


          1. EvilBlueBeaver
            03.06.2023 15:23

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


        1. mayorovp
          03.06.2023 15:23

          Lock-free queue

          А в такой очереди без мьютекса сообщения не дождёшься :-)


          1. nronnie
            03.06.2023 15:23

            "Ждать сообщения из очереди" обычно считается антипаттерном. Кошерный подход это обрабатывать приходящие сообщения через евенты/коллбеки.


            1. mayorovp
              03.06.2023 15:23

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


              1. nronnie
                03.06.2023 15:23
                -1

                Зависит от реализации самой очереди. Если чтение из неё реализовано как неблокирующий вызов, то отдельный "ждущий" поток будет не нужен.


                1. mayorovp
                  03.06.2023 15:23

                  Вы потеряли изначальную задачу: очередь-то нужна чтобы передать данные в другой поток.


                  Ну и я не видел ещё lock-free очередей с асинхронным чтением.


                  1. nronnie
                    03.06.2023 15:23

                    Пожалуй, да, но тут еще изначально сам сценарий недостаточно детально описан. Было бы больше конкретики, я бы, наверное, дал бы больше ответ :) Но, что, вот, мьютексу там не место, это точно. Блокировать все при каждом любом обращении к очереди это совсем уж грубо и примитивно.


          1. EvilBlueBeaver
            03.06.2023 15:23
            -1

            Дождаться-то как раз и несложно. Если у вас такой огромный поток сообщений, то конкуренция будет на вставку, а консьюмер в спинлок попадать не будет. Ну разве что консьюмер выгребает сообщения настолько быстро, что длина очереди в среднем нулю равна.


    1. mvv-rus
      03.06.2023 15:23

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


      1. cupraer
        03.06.2023 15:23

        Лучшая защита критических секций с данными, которые нельзя изменять параллельно, — это полная иммутабельность данных.


        1. nin-jin
          03.06.2023 15:23
          +1

          Лучшая защита - это всё же гарантии компилятора на уровне типов. Вирусная иммутабельность - одна из таких. Но не единственная, и не самая практичная. Например, есть move semantic, есть shared objects, есть 1p1c.


          1. cupraer
            03.06.2023 15:23

            Как мне типы помешают вместо 42 записать 43 в базу из-за гонки?


            1. nin-jin
              03.06.2023 15:23
              +1

              А иммутабельность вам как помешает ерунду в базу писать?


              1. cupraer
                03.06.2023 15:23
                -1

                В принципе — никак не помешает, а данном конкретном частном случае гонки, обсуждаемом в данном треде, — очень даже помешает.

                А вот типы помогаю примерно никогда, а уж в случае высокой конкурентности — и подавно.


                1. mayorovp
                  03.06.2023 15:23

                  Извините, но какой именно частный случай гонки обсуждается в этом треде? Я вообще не вижу в этом треде ни одного примера кода или хотя бы описания ситуации.


                  Если же вернуться к ситуации "вместо 42 записать 43 в базу из-за гонки", то здесь возможны две ситуации:


                  1. у программы есть внутренняя гонка, из-за чего в БД попадает некорректно вычисленное значение;
                  2. у программы есть внешняя гонка (гонка в БД), из-за чего значение в БД некорректно изменяется.

                  Так вот, в первом случае у вас упоминание БД в описании проблемы лишнее. И против гонки могут помочь как иммутабельность (иногда), так и типы (иногда).


                  Во втором случае вам против гонки не помогут никакие языковые средства, поскольку проблема за их пределами. Ну, разве что в каком-нибудь Идрисе найдётся пакет, позволяющий доказывать корректность транзакций БД, и то сомневаюсь.


  1. NickDoom
    03.06.2023 15:23

    Во у меня какой примерчик из личных случаев: https://github.com/NickDoom-IDKFA/UnpLibr

    Полюбуйтесь/попугайтесь :-D Сколько я с этими атомарками возился, жесть как она есть :-D

    И всё равно рандомизированные тесты с рандомизированными задержками до последнего раза (который и пошёл в релиз) мне выявляли всё новые и новые сочетания стейтов, которые я не догадался предусмотреть.

    Либа интереса не получила (и, соответственно, не получила развития по причине отсутствия смысла), но я надеюсь, что хотя бы гол престижа забил, в смысле в релизной версии у меня таки предусмотрены все сочетания состояний со всеми вариантами задержек %)

    И да, sleep там тоже есть, для критических ошибок, когда надо аварийно вырубиться и ждать, когда клиентское ПО соизволит принять нашу отставку (это может быть хоть через сутки, когда оператор нажмёт «ОК» и отпишется руководителю, что архив битым оказался).


    1. mayorovp
      03.06.2023 15:23

      Что-то я там с ходу так и не нашёл никакой многопоточности, где её искать-то?


      1. NickDoom
        03.06.2023 15:23

        Тредов там два, один обеспечивает апи и полностью синхронен с приложением, пользующимся библиотекой, а второй через пачку атомарок получает его хотелки и через пачку атомарок сигнализирует о готовности. Окно распаковки у алгоритма в любом случае одно, поэтому если нужно независимо читать из нескольких мест архива — создаём столько таких «парочек», сколько нужно (но вот тут я не помню, дописал до этого места или нет; даже если нет, то там уже тривиально — обернул все глобалы и вперёд). Я вообще уже не очень хорошо этого пета помню :( Если память не врёт, эти два треда разнесены по двум группам исходников.

        Смысл в этом простой (там вообще вся задача сферовакуумная настолько, что хоть в учебники помещай) — архив распаковывается вовсе не такими кусками, как нужно приложению, да и распаковка, особенно с поиском, может занять вечность. Библиотека кэширует распаковку и выводит её в отдельный тред, чтобы приложение тем временем занималось своими делами. В итоге апи очень похож на istream::open istream::read istream::close (с лёгким налётом FindNext, заради solid-архивов), а тред распаковки пытается обеспечить максимальную готовность для самого базового режима — последовательного чтения требуемого файла.

        Либа вообще во многом «школьная», меня просто (на тот момент) удивило отсутствие аналогичных решений и я написал основу, то есть сам синхронизатор-буферизатор. С прицепленной к нему unrar.dll оно даже могло практически где-то использоваться :)


        1. mayorovp
          03.06.2023 15:23
          +1

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

          Что делает второй поток если у него нет задачи? Надеюсь, не активно ждёт работы?


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


          1. NickDoom
            03.06.2023 15:23

            Я тоже надеюсь, что не протупил до такой степени, но в точности не помню уже ????

            Скорее всего, какой-то костыль типа «спи один квант времени»…


            1. NickDoom
              03.06.2023 15:23

              Чёрт, набрехал я за давностью лет, там SetEvent / ResetEvent / WaitForSingleObject.


    1. nin-jin
      03.06.2023 15:23
      -2

      А взяли бы толковый язык с толковыми абстракциями, и не пришлось бы мучаться:

      import core.time;
      import std.stdio;
      import jin.go;
      
      // provide periodical signals while consumed
      static auto tick( Output!bool signals, Duration dur ) {
      	while( signals.available >= 0 ) {
      		dur.sleep;
      		signals.put( true );
      	}
      }
      
      // provide one signal after timeout
      static void after( Output!bool signals, Duration dur ) {
      	dur.sleep;
      	signals.put( true );
      }
      
      // start tasks on thread-pool
      auto ticks = go!tick( 100.msecs );
      auto booms = go!after( 450.msecs );
      
      for( ;; ) {
      	
      	// consume all pending ticks
      	while( ticks.pending > 0 ) {
      		write( "tick," );
      		ticks.popFront;
      	}
      	
      	// consume boom
      	if( booms.pending > 0 ) {
      		writeln( "BOOM!" );
      		break;
      	}
      	
      	// sleep or do other work
          1.msecs.sleep;
      }

      И нет, это не $mol.


      1. mayorovp
        03.06.2023 15:23

        Такие "абстракции" есть на любом современном ЯП, язык тут менять необязательно.


        Кстати, мне показалось, или ваш код делает активное ожидание на пустом месте?


        1. nin-jin
          03.06.2023 15:23
          -1

          Только не любой язык умеет передавать сообщения без блокировок проверяя thread-safety на этапе компиляции.