Pull to refresh

Comments 73

Интересно, почему нам всегда удавалось в андроид-приложениях обходиться обычным `synchronized {}`, и не было никаких фризов?

Помню, лет 25 назад писал какое-то приложение на Delphi для шифрования файлов. С потоками заморачиваться не хотел, крутил цикл прямо в главном потоке, в OnClick кнопки. А чтобы окно не висло и продолжало реагировать на события просто в цикл добавил Application.ProcessMessages() - работало идеально.

Интересно, почему...

Я придерживаюсь мнения, что не знать почему что то работает так же плохо, как и не знать почему НЕ работает 🙂. Иначе в других условиях, например при существенном увеличении кода проекта, все может начать работать очень плохо, и потребуется полное переписывание проекта чтобы это исправить (и я имею подобный опыт, в тч опыт отказа от таких проектов, чтобы сохранить рассудок).

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

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

Укоротить те же операции IO так, чтобы это можно было делать блокируя главный поток без фризов, невозможно. Как кстати и блокирующе синхронизировать из главного потока транзакции с состоянием в более менее серьезном приложении на слабых устройствах - чтобы выдать 60 кадров в секунду нужно тратить на все не более 16 мс. Похоже что вы просто использовали какой то стэк, частично решающий проблему за вас, и не особо задумывались почему оно работает, и тестировали на мощных девайсах с полной батарейкой. Чем это грозит я описал в предыдущем комментарии.

В целом же, по моему опыту, приложения на iOS работают быстрее аналогов на Android. Как и сама система. Думаю в том числе благодаря грамотной многопоточке.

Не, ну если говорить об IO, то конечно всё было с помощью дополнительного потока с очередью.

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

Это всего лишь малая часть огромного кода, которая может работать лучше.

Также прекрасно осознаю, что "даже плохой код вполне себе работает", может и не так хорошо на слабых девайсах с просевшей батарейкой.

Но это же не значит что мы не должны знать и уметь как лучше?

Я так и не понял, чем это отличается от many producers/single consumer вокруг ConcurrentQueue для синхронного кода или System.Threading.Channels для асинхронного (

Тем что это Task.Run, но не на тредпуле, а на одном треде, внутри нельзя делать Task.Run, получим дедлок.

Я так понял ридми, возможно ошибаюсь.

Очередь как раз на тредпуле. Представьте что мы просто копим очередь из Action и выполняем их последовательно в потоках тредпула, по одному. Либо гляньте реализацию очереди на Task, там вообще все элементарно - выстраивается цепочка из Task используя ContinueWith.

Отдельно про SO и минус на нём - вам вполне корректно указали, что ваше решение не вписывает в SO, нужен реальный код, а не ссылка на либу.

Ваши бенчмарки для меня выглядят сложными и неочевидными, я не уверен, что именно вы меряли.

Хотелось бы увидеть пример кода (более-менее реального) с примитивом синхронизации и ваш способ его заменить вашей библиотекой.

Где синхронизация?

Не знаю как тут ответить. Внутри? 🙂

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

Но вообще в ближайшее время планируется обновление документации библиотеки (и ответов на SO) со сравнением с каким нибудь lock, примером для не Task-based очереди и тп. И думаю было бы неплохо собрать здесь конструктивные комментарии перед таким обновлением.

А пока можете глянуть реализации бенчмарков, там есть пример и для Monitor, и для SerialQueue, и др. Делают они одно и то же.

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

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

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

Мне проще было понять вашу идею, глядя на тесты (при этом я не пишу на C# и изучал с телефона), чем из статьи.

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

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

вокруг да около

Добавил все таки дополнительный блок с описанием очереди, выделил важные моменты в тексте.

никто не разбирается в многопоточности

Ну суть вы уловили 🙂.

вашу идею

Ни в коем случае не присваиваю идею себе - это придумал какой то гений из Apple.

Мне кажется иногда проще использовать стандартные способы синхронизации. Производительность не сильно отличается.

Ну да, O(1) от времени операции это не сильно. Иногда лучше, иногда нет - задача хорошего инженера знать когда и как лучше, и почему.

Но то, КАК вы это делаете..

Такой уж у меня стиль повествования, будем работать над собой. Все таки первый опыт.

Не Apple, они не знакомы с архитектурой.

Да, скоро вы дойдете до того, что можно использовать SMO (или vDSO, как переобозвали в гугле) вместо стека (данные-то в кэше), а операции Interlocked вместо семафоров - гораздо быстрее.

Остается только переписать все на Verilog (чтобы аппаратно проверять диапазоны работы с данными\приоритеты), сделать генератор и, возможно, свою аппаратуру с миллионами IOPS (как на Frontier).

Ну или прочитать об этом.

https://medium.com/@pushkarevvaleriyandreevich

Не Apple, они не знакомы с архитектурой.

А откуда информация если не секрет?

Да, скоро вы дойдете до того ... Остается только переписать все на ...

Не совсем понял как это относится к тематике статьи - механизмам синхронизации. Вы могли бы скинуть код, который можно добавить в мой бенчмарк, и который продемонстрирует лучшие результаты? За PR буду очень благодарен.

А откуда информация если не секрет?

А вы на бенчмарки посмотрите, они отстают даже от intel/arm ).

Не совсем понял как это относится к тематике статьи - механизмам синхронизации. 

Все, о чем вы говорите следует из архитектурных особенностей CPU (как минимум - все синхронизации это прерывания). И пока вы очень далеки от успеха. И это очень связано с SMO/vDSO.

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

Попробуйте Parallel.Foreach(sequential=true) для сравнения. В методе-обработчике приводите объект к требуемому.

Почему вы сделали свой Parallel.Foreach?

Правильно, последовательный запуск может выполнятся с какой угодно скоростью (остаются одни лишь interlocked.add).

А вот ждать выполнения связанной задачи все равно придется через примитивы синхронизации.

Если вы решили исследовать эту тему - попробуйте лучше конструировать граф зависимостей исполнения. И создавать оптимальное выполнение этого графа (с ContinueWith и прочим).

А вы на бенчмарки посмотрите, они отстают даже от intel/arm

По архитектуре софта - Apple лидирует в плане отзывчивости ОС как на мобилках, так и на десктопах.

Parallel.Foreach(sequential=true)

Подскажите, как с помощью Parallel.ForEach реализовать методы Enqueue или DispatchAsync? Чтобы это считалось механизмом сериализации. И желательно с FIFO.

Почему вы сделали свой Parallel.Foreach?

Это просто рефакторинг, он на основе Parallel.ForEach.

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

...механизмом синхронизации.

Больше похоже на ThreadPool, только с опорой на данные (которые поступают в очередь)

Можете сравнить с ThreadPool, например (сделать обертку чтобы осталось пара методов - Equeue(возвращает guid), и Ready (event(guid)). Добавить обработчики по типу.)

А разве у вас в итоге получился не TaskSheduler?

Загуглите LimitedConcurrencyLevelTaskScheduler - это стандартная реализация от майков (в общем коде правда её нет). Ставите 1 поток и можно запускать в нем стандартные Task'и, которые так же будут друг за другом выполняться в пуле.

В статье я указывал

при всем разнообразии средств синхронизации, отсутствует данное решение из коробки, хоть его и очень просто реализовать имеющимися средствами ... и через SemaphoreSlim, и через ActionBlock, и банально объединяя Task в цепочку вызовов ...

Так что да, реализаций то много, но

отсутствует данное решение из коробки,

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

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

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

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

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

Как это не могу? А вы юнит тесты смотрели или бенчмарк? И там и там все прекрасно читается.

Если есть проблема то можете создать PR с падающим тестом, посмотрим.

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

вы можете читать только в одном потоке

Если использовать подход с неизменяемыми данными (immutable) то можно читать из любого потока

если объем работы воркеров которые обсчитывают стейт превысит возможности одного потока то что вы будете делать

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

в первую очередь это ограничение метода а не фича. Вы ошибаетесь, readerwriter lock подразумевает выполнение нескольких операций чтения одновременно

Речь про аналоги примитивов типа Monitor, SpinLock и тп. ReaderWriterLock все таки это более сложная конструкция, построенная на примитивах, и про него я даже отдельно написал:

...полагаю реализации ReaderWriterSerialQueue и ReaderWriterSpinLock были бы куда производительнее..

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

Но проверять данную гипотезу уже не буду.

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

Спасибо. Да, статья примерно про это же. Но к сожалению:

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

  • тесты производительности только для вызовов пустых функций.

  • реализация NoDedicatedThreadQueue похуже чем моя SerialQueue.

Пример с java крайне не удачный. Начать хотябы с того что в том же spring есть project reactor, который позволяет писать в реактивном стиле не требуя иметь thread per request. И там же есть r2dbc для подключения к базе. Если есть действительно потребность минимизировать потребление ресурсов то можно посмотреть на vertx или quarkus, у которых так же есть варианты с асинхронностью.

В java ваш подход ложится в single thread executor service. Но как утилизировать ресурсы многоядерного окружения в вашем подходе?

project reactor, который позволяет писать в реактивном стиле не требуя иметь thread per request

Про thread per request я даже не писал, потому что это уже за гранью разумного и хочется верить что так уже никто не делает.

И там же есть r2dbc для подключения к базе

Как не оборачивай блокирующий код в какой нибудь CompletableFuture, он не перестанет быть блокирующим. Просто будет асинхронно блокировать поток пула потоков, чем вызывать создание нового потока в пуле.

Если есть действительно потребность минимизировать потребление ресурсов...

Так речь про стандарты разработки. Многие так делают? Да и зачем, если для минимизации потребления ресурсов есть куда более удачные (простые, производительные) технологии?

Но как утилизировать ресурсы многоядерного окружения?

Последовательная очередь это механизм синхронизации, и делает он это (утилизирует ресурсы) крайне эффективно, это видно по графикам. Считайте что это то же самое что synchronized только лучше. Вы же не оборачиваете все функции в synchronized? А только те места, которые требуют синхронизации доступа к какому то ресурсу.

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

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

"Одной из основных причин является то, что в Java Spring принято вызывать блокирующие операции ввода-вывода (например JDBC не имеет не блокирующего интерфейса вовсе) и блокирующие же механизмы синхронизации."

Кем это принято? Для какого пула задач? В каких ситуациях? Даже на спринге можно написать вполне себе неплохое реактивное приложение. Все для это есть. И я видел много примеров в том числе на работе.

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

Я бы сказал как не оборачивай в асинхронный код все равно упрешься в ресурсы базы и её возможности. И какой язык не возьми все равно упрешься в это. И как может переход на nodejs что то дать вообще не ясно. Или там какой то другой асинхрон? Там нет пула потоков? Ограничения на коннекшены? База при работе из nodejs вдруг становиться резиновая?

Мой мессадж про многоядерное окружение касался той темы когда вам нужно чтобы условно ресурсы каждого цп были утилизированы. То есть у вас есть допустим пул объектов которые хотим шарить между потоками. Тут получается нужно делать n single thread executor и на каждом запускать задачи и как то потоки задач разделять по этим n экзекьюторам.

По поводу r2dbc. postgres https://www.postgresql.org/docs/current/libpq-async.html имеет интерфейсы для асинхронной работы. И r2dbc postgres драйвер использует их, поэтому тут про блокировку на уровне java просто некорректно говорить. Там вызов PQsendQuery отправляет query, PQgetResult получает результат.

Отвечу сразу на оба коммента.

Даже на спринге можно написать вполне себе неплохое реактивное приложение. Все для это есть. И я видел много примеров в том числе на работе.

...

По поводу r2dbc. postgres https://www.postgresql.org/docs/current/libpq-async.html имеет интерфейсы для асинхронной работы. 

Да, не обратил внимание что речь про r2dbc, у него можно, но почему то мало кто его использует, и в интернете для продакшена часто не советуют использовать. Если "можно написать вполне себе" то почему большинство так не делает? Я говорил про стандарты разработки, то есть то, как делает большинство в продакшене.

Я бы сказал как не оборачивай в асинхронный код все равно упрешься в ресурсы базы и её возможности.

То есть можно "говнокодить" на уровне приложения и не следить за его производительностью? Не согласен совсем. И я привел пример почему, на основе опыта Paypal.

И как может переход на nodejs что то дать вообще не ясно. Или там какой то другой асинхрон?

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

То есть у вас есть допустим пул объектов которые хотим шарить между потоками. Тут получается нужно делать n single thread executor и на каждом запускать задачи и как то потоки задач разделять по этим n экзекьюторам.

Повторяю, нужно использовать обычный пул потоков, и при работе с состоянием, к которому требуется синхронизировать доступ, можно использовать последовательную очередь, если операции могут длится долго, либо спинлок если супер быстрые. Примерный код на C#:

readonly SerialQueue serialQueue = new SerialQueue()

// функция вызывается параллельно в пуле потоков для каждого запроса
async Task<Response> HandleRequest(Request request) {
  ...
  await serialQueue.Enqueue(() => {
    // обработка состояния, требующего синхронизации
  });
  ...
}

"То есть можно "говнокодить" на уровне приложения и не следить за его производительностью? Не согласен совсем. И я привел пример почему, на основе опыта Paypal."

Опыт paypal ничего нам не говорит о java и nodejs, так как мы не знаем что они там и как переписали и какого уровня по и разработчики были на java и на nodejs. На любом языке можно написать плохо.

Что касается асинхронного и синхронного подхода, то средства нужно выбирать под задачу. Если у вас есть nfr с большой нагрузкой то возможно нужно будет использовать асинхронный код. Если же у вас их нет, то асинхронный код - обычный оверинжиниринг, который ни к чему кроме увелечения стоимости разработки и поддержки не ведёт. И да в реальных тестах r2dbc не всегда может показать результаты лучше, так как упретесь вы скорее всего раньше в базу.

Подход ваш понятен, попробую на java потестить и сравнить

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

Если на Java «по хорошему» требует невероятных усилий, то это плохая технология, от которой следует отказаться.

Принцип KISS.

Вы просто подменяете тезисы. Я не писал про невероятные усилия.

Попробовал на java https://github.com/pkokoshnikov/habr_803273

single thread executor работает медленее чем обычный synchronized в 2 раза. Хоть на долгих, хоть на быстрых операциях. SpinLock работает так же как и ReentrantLock или synchronized. Поэтому для java ваши выводы не применимы

Они применимы для любого языка, так как имеют O(1) по времени от длины операции, в отличие от блокирующих способов. Боюсь что вы, во первых, не совсем поняли о чем в статье речь, во вторых написали плохие тесты.

Спасибо за тесты. Я не на 100% уверен, но похоже, что Parallel.For использует гораздо больше потоков, чем процессоров.

Можете прогнать тесты на `availableProcessors * 100`, например? Я так понимаю, что посыл автора в том, что чем больше степень concurrency, тем выше цена синхронизации.

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

В статье много посылов, но по части синхронизации - имеют смысл только два механизма, спинлоки примерно до 0.1 мс и очереди для остальных.

Даже на C# уже асинхронность встроена во все фреймворки, является стандартом и отличается от синхронного кода лишь словом await.

У очередей есть фундаментальная проблема - что делать при переполнении

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

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

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

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

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

Про "вообще ничего не нужно" пожалуй не соглашусь, что то да нужно.

Почему память то?

Условно в очередь я добавляю по 1000 элементов в секунду, а разгребаю по 10.

Это тоже переполнение.

Еще раз, с тем же Monitor, когда в секцию lock пытаются войти 1000 потоков в секунду а выходит оттуда 10, 990 потоков в секунду будут блокироваться и пул будет создавать новые пока не закончится память.

Это ситуация не здоровая в любом случае, и возникла скорей всего из за критического бага.

Слона в вакууме обсуждаем, я просил у вас примеры кода "до" и "после", но ни одного примера так и не увидел.

Кейс нужен, чтобы понять что на что заменяется. И вот это уже можно мерять и оценивать.

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

Какой у меня в приложении должен быть код, чтобы ваша библиотека решала мою "проблему"?

Я уже не знаю как еще ответить вам, кинул и ссылку на бенчмарки, и ссылку на readme где после примеров с очередями указано:

Previous examples do the same as the next one with Monitor (lock):

readonly object locker = new object();

async Task SomeAsyncMethod()
{
    lock(locker) {
         // synchronized code
    }
}

Что переводится как "Предыдущие примеры (с очередями) делают то же самое, что следующий с Monitor (lock):"

Так, я решил попробовать синхронный вариант, взял https://raw.githubusercontent.com/gentlee/SerialQueue/master/SerialQueue/SerialQueue.cs

Накидал вот такой код (с использованием библиотеки BenchmarkDotNet, чтобы посчитать эффективность хотя бы линейного использования)

https://gist.github.com/MonkAlex/3ee81313728b3b7227a8a223f64973c3

Код с локом ожидаемо работает и довольно шустро (т.к. линейный подход в один поток), а ваш код оказывается возвращает управление не выполнив работу, и мой метод Assert валится с разным числом выполненых обработчиков.

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

В текущем виде синхронная реализация некорректна и использовать её нельзя.

ПС: асинхронную\многопоточную без понятия как измерять, т.к. оно будет зависеть от очень большого числа случайных переменных на ПК.

Последовательные очереди асинхронны так как не блокируют поток в ожидании очереди. Поэтому нельзя заменить Monitor на SerialQueue не добавляя асинхронность. Можно конечно вызвать синхронную операцию DispatchSync, но тогда потеряется весь смысл.

Эффективная синхронизация не может быть синхронной, только для супер коротких операций со SpinLock.

Про это статья и написана. И прочитайте секцию "выводы" еще раз.

асинхронную\многопоточную без понятия как измерять

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

Вы живой человек или чатгпт?

Я прошу примеры, вы говорите "вот примеры".

Я пытаюсь ими пользоваться - вы говорите, так нельзя, в этом нет смысла.

В чём есть смысл то? Когда мне нужен ваш код, в каких сценариях использования?

ПС: ваши бенчи я не понял, поэтому и задаю вопросы.

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

По ссылке выше ( https://gist.github.com/MonkAlex/3ee81313728b3b7227a8a223f64973c3 ) я ошибся вызовом. Когда сделал синхронный вызов, удалось померять производительность, результаты по ссылке приложил - лок работает быстрее.

По вашему примеру в статье попытался сделать вот такое https://gist.github.com/MonkAlex/f9df69aa26ae06896aa374563896dba0

Результаты по ссылке тоже приложил, лок опять быстрее, примерно на порядок.

И это полностью соответствует тому, что написано в статье, и результатам моего бенчмарка.

А теперь замените во втором тесте counter++ на операцию длиной 5 мс.

И будет у меня бенчмарк операции длиной 5мс, а не механизма "синхронизации".

Сейчас накладные в наносекундах, и их "съест" дополнительная логика в 5мс.

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

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

ну вот видите, фундаментальные причины очень сильны, это вам ещё лаги не показали

>>Про "вообще ничего не нужно" пожалуй не соглашусь, что то да нужно.

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

React же решает необходимость применения известной функции на изменяющиеся входные данные, с соблюдением консистентности результата. Кэши же и пр. вещи, вносящие синхронизацию, зависят от реализации. Там больше нужно как можно быстрее прервать запущенный расчёт при изменении входных данных. Если взять классическую схему применения, то вполне можно вписать в поток, который отслеживает изменения, ведёт пересчёт и убедившись в консистентности результата отправляет его в GUI на вывод. Без выхода кешей за этот поток синхронизация тоже не требуется. Если не считать "лаги на рассчёт" тормозами, то обновление гуи будет мгновенным.

По поводу юзер-кода то конечно, тот же react в user-коде вообще работает в одном потоке и там даже не обязателен функциональный подход, как и в NodeJS. Он полезен далее для оптимизации рендеринга в недрах реакта. То что в многопоточке можно совсем без синхронизаций используя функциональный подход - в теории да, на практике может быть довольно медленно и я таким к сожалению пока не занимался, хотя знаю что уже давно придумали коллекции на деревьях, которые копируются супер быстро.

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

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

Т.е. как я и писал выше - аналог Task.Run и Dispatcher.BeginInvoke , когда зачем то хочется делать это в рамках одного треда.

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

зачем то (с)

в рамках одного треда

Ну как то вам не помогло все таки)

аналог Task.Run и Dispatcher.BeginInvoke

А это он вообще писал про Concurrent и Main queues)

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

Ни разу в работе не сталкивался с каналами, поэтому уверенно не скажу.

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

Добавить описание из доки Apple - хорошая идея. Спасибо.

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

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

Это именно что замена "лока". По поводу остального:

  1. Межпроцессная блокировка это еще большая ерунда чем межпоточная. Длительных блокировок потоков вообще не должно быть.

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

  3. RWLock - тоже скорей всего реализация будет лучше если использовать SpinLock и SerialQueue.

Sign up to leave a comment.

Articles