Здравствуйте, vdimas, Вы писали:
G>>1) Переключение контекста не бесплатное V>Для потоков одного процесса должно быть бесплатным.
Переключение в режим ядра и обратно никогда не бывает бесплатным.
G>>2) Каждый поток кушает 1МБ под стек минимум V>Настраивается.
С учётом служебных структур и прочего — в Линуксе около 32Кб. Но это экстремально мало.
V>И эта же проблема присутствует в любой реализации "зеленых потоков".
В Го стек занимает 2048 байт при создании, и потом по необходимости растёт.
S>>Зачем тогда async/await N>С точки зрения логики управления выполнением — именно для тех случаев, когда шедулер узнал, что ответа для await сейчас нет и надо дать кому-то управление. N>С точки зрения исходного кода — чтобы писать его максимально линейно. N>Переключений в нём, в идеале, столько же, сколько в аналогичном коде на коллбэках или промисах.
Вероятно речь о том, что раньше тип task был классом, то теперь может быть (или есть всегда) структура,
соотв. меньше напряг на gc. Например, когда запускаем задачу, которая выполняется синхронно, т.е.
в том же потоке и получается, что task создали зря, лишний напряг на gc. Какое отношение это имеет к дискуссии --
Здравствуйте, Sharov, Вы писали:
S>>>Зачем тогда async/await N>>С точки зрения логики управления выполнением — именно для тех случаев, когда шедулер узнал, что ответа для await сейчас нет и надо дать кому-то управление. N>>С точки зрения исходного кода — чтобы писать его максимально линейно. N>>Переключений в нём, в идеале, столько же, сколько в аналогичном коде на коллбэках или промисах.
S>async\await и есть по сути promise.
Да. Но в случае await просто пишете x = await y(), а для промисов (я имею в виду стиля JS) надо конструировать что-то вроде
p = new Promise(function() { y(); }).then(function(result) { x = result; });
S>>> https://stackoverflow.com/questions/66387225/awaiting-a-single-net-event-with-a-valuetask N>>Я что-то не могу это раскурить. Какой смысл в его применении?
S>Вероятно речь о том, что раньше тип task был классом, то теперь может быть (или есть всегда) структура, S>соотв. меньше напряг на gc. Например, когда запускаем задачу, которая выполняется синхронно, т.е. S> в том же потоке и получается, что task создали зря, лишний напряг на gc. Какое отношение это имеет к дискуссии --
Ну меньше напряг — всегда полезно но таки да, не в тему.
Здравствуйте, Serginio1, Вы писали:
N>>Эээ... после выполнения захвата лока (речь об этом, да?) как раз лучше не отдавать управление, а сделать максимум действий во время захвата. А до него — если лок занят — то раз ждём, надо оповестить шедулер о том, что есть отличный повод запустить другую задачу. S> Я как раз про то, что если lock короткий, то не стоит передавать управление на другой поток.
Так передавать когда? До него или после? В случае удачного захвата или неудачного?
Не хочу искать, что в Windows. В Linux это выглядит (для внутрипроцессных мьютексов): читаем переменную, которая собственно представляет мьютекс для процесса. Могут быть варианты: 0, 1, 2. 2 значит, что мьютекс залочен и кто-то уже стоит в очереди на него, тогда сразу идём в ядерный вызов становиться в хвост. Если 0 или 1, делаем сколько-то (грубо говоря, 100) попыток захватить его через команду процессора (считай, cmpxchg), если получилось (было 0 и мы записали 1) — ok, работаем, иначе кончились попытки — снова становимся в очередь уже через ядерный вызов.
Освобождение похоже: читаем ячейку; 2 — значит, за нами стоят в очереди и ядерный вызов необходим, чтобы его разбудить. 1 — записываем туда 0 и знаем, что если кто-то захочет, он придёт без дополнительной синхронизации.
Так вот пока крутится цикл с cmpxchg — ядро не дёргаем и ему нет причины задуматься, переключать ли на кого-то ещё, процесс себе продолжает работать. Условие "не стоит передавать управление", насколько я вижу, выполнено идеально.
S>https://ru.wikipedia.org/wiki/%D0%9F%D0%B5%D1%80%D0%B5%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BA%D0%BE%D0%BD%D1%82%D0%B5%D0%BA%D1%81%D1%82%D0%B0 S>
S>Синхронизирующие примитивы ядра. Мьютексы, Семафоры и т. д. Это и есть основной источник проблем с производительностью. Недостаточно продуманная работа с синхронизирующими примитивами может приводить к десяткам тысяч, а в особо запущенных случаях — и к сотням тысяч переключений контекста в секунду. [источник не указан 2477 дней]
Да, проблема есть (хотя про "основной источник проблем с производительностью" тут загнули, случай он разный бывает). Но вы никак от этого не избавитесь ни шедулингом, ни переходом на await, пока вам потребуется ровно та же синхронизация.
S>>>Для примера в эпоху до async/await поток ждет выполнения асинхронной операции. S>>>async/await берет на себя сохранения данных внутри класса (стек не нужен) и строит автомат и тот же поток который выполнял данную задачу, запускает другую. Нет никаких переключений.
N>>А что, вот эти все "берёт на себя сохранения данных внутри класса" и аналогичное восстановление, по-вашему, не переключение? А что оно такое тогда? S> Там нет восстаеовления ибо данные хранятся в куче.
Если так, то та же цена реально размазана по остальной работе: с данными в куче всегда дороже работать, чем с данными на стеке или тем более в регистрах: аллокация, GC где-то после, разыменование указателей (спрятанных в ссылках дотнетов), заметно худшее кэширование, потому что в стеке данные сидят плотно, и наверняка ещё и пара сотен байт вершины стека в кэшах процессора.
Не может быть квадратных кругов и круглых квадратов. Если с данными надо работать, то для этого нужен доступ к ним. Если нужен доступ, их надо прочитать из памяти и записать в память, с соответствующей ценой. Я бы предпочёл, чтобы компиляция хранила максимум в регистрах процессора, а что не помещается — максимум на стеке: не будет дурных потерь скорости. А если при этом само переключение формально дороже — тоже не страшно, записать компактно пару сотен байт и потом прочитать — эффективнее, чем размазывать их по десяткам кэш-строк.
N>>С точки зрения логики управления выполнением — именно для тех случаев, когда шедулер узнал, что ответа для await сейчас нет и надо дать кому-то управление. N>>С точки зрения исходного кода — чтобы писать его максимально линейно. S> Линейно то и раньше писали, только внутри были всякие евенты и остановка потока.
Я говорю про варианты без такой остановки (если переключение, то оно минимально видно в ОС).
N>>Переключений в нём, в идеале, столько же, сколько в аналогичном коде на коллбэках или промисах. S> Ну в итоге то колбеки запускаются из пула потоков.
Ну это уже дотнетовая специфика и не обязательно так везде. И это не обязательно удобно. Я во многих случаях хотел бы ограничить такое явно только тем же тредом, который вызвал исходную операцию.
S>>>Ну и замена всяких Lock на ManualResetValueTaskSource S>>>http://rsdn.org/forum/dotnet/8030645.1
Если оно сделано умно (с попытками захвата до переключения) — отлично.
S>>>Конечно зависит от длительности задач, но если задачи непродолжительные, то пул потоков может и не переключаться а выполнять очередь заданий. N>>Тоже не понимаю, при чём тут эта реплика. S> В том, что с использованием пула потоков сводит к минимуму переключение потоков
Так не от желания конкретной задачи тут зависит, а от того, будет ли тот лок свободен.
Здравствуйте, netch80, Вы писали:
S>>В таком случае зеленые потоки будут просто не нужны. Ну т.е. в ядре ОС выполнять код соотв. стороннего планировщика. N>И кто пустит посторонний код в ядро?
Можно eBPF использовать
N>Похожие разработки как раз были. Во FreeBSD игрались с подходом "scheduler activations". Это выглядело так: если нить блокируется в ядре, ядро запускает указанный процессом пользовательский код с указанием "а подумай, не переключиться ли на что-то ещё". Пользовательский код управляет тем, какие из видимых ниток должны исполняться. N>Разработка шла приблизительно в 1999-2002. После отказались и сделали тупо 1:1, все нити процесса известны ядру и оно их переключает. Вот не справились со сложностью.
Проблема в том, что переключения контекстов уж слишком дорогие.
Здравствуйте, netch80, Вы писали:
N>Здравствуйте, Serginio1, Вы писали:
N>>>Эээ... после выполнения захвата лока (речь об этом, да?) как раз лучше не отдавать управление, а сделать максимум действий во время захвата. А до него — если лок занят — то раз ждём, надо оповестить шедулер о том, что есть отличный повод запустить другую задачу. S>> Я как раз про то, что если lock короткий, то не стоит передавать управление на другой поток.
N>Так передавать когда? До него или после? В случае удачного захвата или неудачного?
При удачном захвате стоит дождаться освобождения, что бы другие потоки могли воспользоваться свободным монитором
N>Да, проблема есть (хотя про "основной источник проблем с производительностью" тут загнули, случай он разный бывает). Но вы никак от этого не избавитесь ни шедулингом, ни переходом на await, пока вам потребуется ровно та же синхронизация.
Я тебе привел пример аналога LockAsync. Поток ничего не ждет, а по событию запускается из пула потоков. Вот пример асинхронной очереди AsyncProducerConsumerCollection
Просто привел пример с использованием ValueTask вместо Task
S>>>>Для примера в эпоху до async/await поток ждет выполнения асинхронной операции. S>>>>async/await берет на себя сохранения данных внутри класса (стек не нужен) и строит автомат и тот же поток который выполнял данную задачу, запускает другую. Нет никаких переключений.
N>>>А что, вот эти все "берёт на себя сохранения данных внутри класса" и аналогичное восстановление, по-вашему, не переключение? А что оно такое тогда? S>> Там нет восстаеовления ибо данные хранятся в куче.
N>Если так, то та же цена реально размазана по остальной работе: с данными в куче всегда дороже работать, чем с данными на стеке или тем более в регистрах: аллокация, GC где-то после, разыменование указателей (спрятанных в ссылках дотнетов), заметно худшее кэширование, потому что в стеке данные сидят плотно, и наверняка ещё и пара сотен байт вершины стека в кэшах процессора.
Угу который при переходе на новый поток сбрасывается.
У меня вопрос если все так прекрасно с переключением потоков, то зачем делают все, что бы отказаться от потоков и использовать очередь потоков?
N>Не может быть квадратных кругов и круглых квадратов. Если с данными надо работать, то для этого нужен доступ к ним. Если нужен доступ, их надо прочитать из памяти и записать в память, с соответствующей ценой. Я бы предпочёл, чтобы компиляция хранила максимум в регистрах процессора, а что не помещается — максимум на стеке: не будет дурных потерь скорости. А если при этом само переключение формально дороже — тоже не страшно, записать компактно пару сотен байт и потом прочитать — эффективнее, чем размазывать их по десяткам кэш-строк.
Ну класс если используешь Value типы тоже будут компактно расположены, а не валуе типы что на стеке, что не на нем все равно в куче
N>>>С точки зрения логики управления выполнением — именно для тех случаев, когда шедулер узнал, что ответа для await сейчас нет и надо дать кому-то управление. N>>>С точки зрения исходного кода — чтобы писать его максимально линейно. S>> Линейно то и раньше писали, только внутри были всякие евенты и остановка потока.
N>Я говорю про варианты без такой остановки (если переключение, то оно минимально видно в ОС).
N>>>Переключений в нём, в идеале, столько же, сколько в аналогичном коде на коллбэках или промисах. S>> Ну в итоге то колбеки запускаются из пула потоков.
N>Ну это уже дотнетовая специфика и не обязательно так везде. И это не обязательно удобно. Я во многих случаях хотел бы ограничить такое явно только тем же тредом, который вызвал исходную операцию.
S>>>>Ну и замена всяких Lock на ManualResetValueTaskSource S>>>>http://rsdn.org/forum/dotnet/8030645.1
S>>>> https://stackoverflow.com/questions/66387225/awaiting-a-single-net-event-with-a-valuetask
N>>>Я что-то не могу это раскурить. Какой смысл в его применении? S>> Смысл в том, что замена lock на lockAsync. То есть нет никакого ожидания потока
N>Если оно сделано умно (с попытками захвата до переключения) — отлично.
S>>>>Конечно зависит от длительности задач, но если задачи непродолжительные, то пул потоков может и не переключаться а выполнять очередь заданий. N>>>Тоже не понимаю, при чём тут эта реплика. S>> В том, что с использованием пула потоков сводит к минимуму переключение потоков
N>Так не от желания конкретной задачи тут зависит, а от того, будет ли тот лок свободен.
Вот ссылочка из предыдущего сообщения AsyncSemaphore
Если lock занят то возвращается Task и ожидается пока не вызовется SetResult
Никакой остановки потока нет. Да внутри конечно есть короткий lock который нужно заменять на SpinLock
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
S>У меня вопрос если все так прекрасно с переключением потоков, то зачем делают все, что бы отказаться от потоков и использовать очередь потоков?
Очередь потоков? Пул потоков, в смысле?
Из-за того, что системные потоки очень дорого запускать с нуля.
Здравствуйте, Serginio1, Вы писали:
N>>>>Эээ... после выполнения захвата лока (речь об этом, да?) как раз лучше не отдавать управление, а сделать максимум действий во время захвата. А до него — если лок занят — то раз ждём, надо оповестить шедулер о том, что есть отличный повод запустить другую задачу. S>>> Я как раз про то, что если lock короткий, то не стоит передавать управление на другой поток.
N>>Так передавать когда? До него или после? В случае удачного захвата или неудачного? S>При удачном захвате стоит дождаться освобождения, что бы другие потоки могли воспользоваться свободным монитором
Вероятность прерывания в этот момент для интерактивной нитки (который больше спит и реагирует на события, чем ест процессор) крайне мала (при сколь-нибудь умном шедулере, разумеется).
Более того, в случае await-based построения работы вероятность насильственного переключения со стороны ОС выше, если нет возможности её явно проинструктировать "тут воздержись, а тут больше свободы" — если гружёный процесс держит всю активность в нескольких нитках по количеству хартов — с точки зрения ОС это одна неуправляемая прожора, и когда переключать на соседей — она просто не в курсе.
N>>Да, проблема есть (хотя про "основной источник проблем с производительностью" тут загнули, случай он разный бывает). Но вы никак от этого не избавитесь ни шедулингом, ни переходом на await, пока вам потребуется ровно та же синхронизация. S> Я тебе привел пример аналога LockAsync. Поток ничего не ждет, а по событию запускается из пула потоков. Вот пример асинхронной очереди S>AsyncProducerConsumerCollection S>Просто привел пример с использованием ValueTask вместо Task
Так с точки зрения ОС что поможет тут осознать, когда можно переключать на другую нить вообще другого процесса, а когда лучше этого не делать, потому что захвачен важный лок (и даже в случае асинхронных локов это блокирует другие задачи)?
Я б ожидал каких-то явных системных вызовов, но тут про них ни слова.
N>>Если так, то та же цена реально размазана по остальной работе: с данными в куче всегда дороже работать, чем с данными на стеке или тем более в регистрах: аллокация, GC где-то после, разыменование указателей (спрятанных в ссылках дотнетов), заметно худшее кэширование, потому что в стеке данные сидят плотно, и наверняка ещё и пара сотен байт вершины стека в кэшах процессора. S> Угу который при переходе на новый поток сбрасывается.
Кто сбрасывается? Кэш процессора? Зачем? Даже с мерами против Meltdown/Spectre кэш сбрасывается при переключении между процессами, а не между нитками одного процесса. Хотя о чём и, главное, чем думают тут в MS, я не в курсе.
S>У меня вопрос если все так прекрасно с переключением потоков, то зачем делают все, что бы отказаться от потоков и использовать очередь потоков?
Кто сказал, что "всё прекрасно"? Я не знаю, откуда и как вы делаете такие выводы.
И что это "все", что делают чтобы отказаться? В основном делают там, где просто неудобно/дорого по другим причинам возлагать это на ОС.
Да, есть цена на переключение, даже вход и выход из системного вызова чего-то стоит, и на Windows эта цена сильно выше, чем на Linux. Может, потому для Windows и стараются сократить эти переключения. В Linux я не вижу такого активного стремления, если где-то такое делают, то в рантаймах со сложной собственной логикой и полностью "управляемым" выполнением (Go, Erlang).
N>>Не может быть квадратных кругов и круглых квадратов. Если с данными надо работать, то для этого нужен доступ к ним. Если нужен доступ, их надо прочитать из памяти и записать в память, с соответствующей ценой. Я бы предпочёл, чтобы компиляция хранила максимум в регистрах процессора, а что не помещается — максимум на стеке: не будет дурных потерь скорости. А если при этом само переключение формально дороже — тоже не страшно, записать компактно пару сотен байт и потом прочитать — эффективнее, чем размазывать их по десяткам кэш-строк.
S>Ну класс если используешь Value типы тоже будут компактно расположены, а не валуе типы что на стеке, что не на нем все равно в куче
То есть в дотнет ещё не завезли escape analysis? (сомневаюсь)
S>>>>>Конечно зависит от длительности задач, но если задачи непродолжительные, то пул потоков может и не переключаться а выполнять очередь заданий. N>>>>Тоже не понимаю, при чём тут эта реплика. S>>> В том, что с использованием пула потоков сводит к минимуму переключение потоков
N>>Так не от желания конкретной задачи тут зависит, а от того, будет ли тот лок свободен. S>Вот ссылочка из предыдущего сообщения AsyncSemaphore
S>Если lock занят то возвращается Task и ожидается пока не вызовется SetResult S>Никакой остановки потока нет. Да внутри конечно есть короткий lock который нужно заменять на SpinLock
Почему это "нужно заменять"? Обычный lock настолько неэффективен?
Здравствуйте, Cyberax, Вы писали:
S>>>В таком случае зеленые потоки будут просто не нужны. Ну т.е. в ядре ОС выполнять код соотв. стороннего планировщика. N>>И кто пустит посторонний код в ядро? C>Можно eBPF использовать
Я не уверен, что на нём можно написать шедулер. Хотя если есть возможность оперирования с конкретными байтиками и всё уложить на эту логику, то может пойти...
N>>Похожие разработки как раз были. Во FreeBSD игрались с подходом "scheduler activations". Это выглядело так: если нить блокируется в ядре, ядро запускает указанный процессом пользовательский код с указанием "а подумай, не переключиться ли на что-то ещё". Пользовательский код управляет тем, какие из видимых ниток должны исполняться. N>>Разработка шла приблизительно в 1999-2002. После отказались и сделали тупо 1:1, все нити процесса известны ядру и оно их переключает. Вот не справились со сложностью. C>Проблема в том, что переключения контекстов уж слишком дорогие.
Проблема нового или старого решения? По крайней мере горизонтальное переключение в пределах одного процесса вроде бы дешёвое.
Здравствуйте, mrTwister, Вы писали:
T>А почему, собственно, потоки операционной системы не могут быть столь же эффективны, как и горутины? То есть почему разработчики OS тоже не могут сделать динамический стек и другие оптимизации?
Как ты это себе видишь на уровне ОС? Убрать относительно дорогие потоки не получится, хотя бы потому что уже 100500 программ их используют. Плюс у горутин (и других аналогов) есть свои ограничения и недостатки в дополнение к потокам они норм вместо них нет. И чем реализация на уровне ОС будет лучше библиотечной?
T>А почему, собственно, потоки операционной системы не могут быть столь же эффективны, как и горутины? То есть почему разработчики OS тоже не могут сделать динамический стек и другие оптимизации?
Что касается динамического стека тут уже написали. Что касается "других оптимизаций", то поток — это прежде всего контекст процессора. Поток операционной системы — это два контекста процессора — юзермодный и кернелмодный, чтоб их оба переключить требуется переход в кернелмод и обратно. "Другие оптимизации" aka green threads — это лишь юзермодный контекст, соответственно потоком операционной системы быть не могут. Но библиотеки, позволяющие реализовать такую функциональность для unmanaged кода существуют — начиная от реальных зеленых потоков (хотя я эту либу не юзал никогда) и заканчивая корутинами в плюсах. В принципе как часть АПИ тоже есть как минимум пример — т.н. fiber в винде но особой популярности они не получили.
Как много веселых ребят, и все делают велосипед...
N>>>Да, проблема есть (хотя про "основной источник проблем с производительностью" тут загнули, случай он разный бывает). Но вы никак от этого не избавитесь ни шедулингом, ни переходом на await, пока вам потребуется ровно та же синхронизация. S>> Я тебе привел пример аналога LockAsync. Поток ничего не ждет, а по событию запускается из пула потоков. Вот пример асинхронной очереди S>>AsyncProducerConsumerCollection S>>Просто привел пример с использованием ValueTask вместо Task
N>Так с точки зрения ОС что поможет тут осознать, когда можно переключать на другую нить вообще другого процесса, а когда лучше этого не делать, потому что захвачен важный лок (и даже в случае асинхронных локов это блокирует другие задачи)? N>Я б ожидал каких-то явных системных вызовов, но тут про них ни слова.
Ту тут главное tcs.TrySetResult(item);
И фцфше может продолжаться в этом же потоке в зависимости от условий создания TaskCompletionSource http://rsdn.org/forum/dotnet/7639144.1
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
https://docs.microsoft.com/ru-ru/dotnet/api/system.threading.tasks.taskcreationoptions?view=net-5.0#System_Threading_Tasks_TaskCreationOptions_RunContinuationsAsynchronously
N>>>Если так, то та же цена реально размазана по остальной работе: с данными в куче всегда дороже работать, чем с данными на стеке или тем более в регистрах: аллокация, GC где-то после, разыменование указателей (спрятанных в ссылках дотнетов), заметно худшее кэширование, потому что в стеке данные сидят плотно, и наверняка ещё и пара сотен байт вершины стека в кэшах процессора. S>> Угу который при переходе на новый поток сбрасывается.
N>Кто сбрасывается? Кэш процессора? Зачем? Даже с мерами против Meltdown/Spectre кэш сбрасывается при переключении между процессами, а не между нитками одного процесса. Хотя о чём и, главное, чем думают тут в MS, я не в курсе.
Ну кэш то первого уровня точто сбросится ибо слишком мал. И все зависит от используемых данных потока.
S>>У меня вопрос если все так прекрасно с переключением потоков, то зачем делают все, что бы отказаться от потоков и использовать очередь потоков?
N>Кто сказал, что "всё прекрасно"? Я не знаю, откуда и как вы делаете такие выводы. N>И что это "все", что делают чтобы отказаться? В основном делают там, где просто неудобно/дорого по другим причинам возлагать это на ОС. Асинхронность и производительность
Утверждают, что выхлоп есть
N>Да, есть цена на переключение, даже вход и выход из системного вызова чего-то стоит, и на Windows эта цена сильно выше, чем на Linux. Может, потому для Windows и стараются сократить эти переключения. В Linux я не вижу такого активного стремления, если где-то такое делают, то в рантаймах со сложной собственной логикой и полностью "управляемым" выполнением (Go, Erlang).
Ну сейчас практически все сервера в облаке на линуксе, но там же и asinc await используются на прополуюю ибо основная то работа это запрос к базе данных и прочие асинхронные операции
N>>>Не может быть квадратных кругов и круглых квадратов. Если с данными надо работать, то для этого нужен доступ к ним. Если нужен доступ, их надо прочитать из памяти и записать в память, с соответствующей ценой. Я бы предпочёл, чтобы компиляция хранила максимум в регистрах процессора, а что не помещается — максимум на стеке: не будет дурных потерь скорости. А если при этом само переключение формально дороже — тоже не страшно, записать компактно пару сотен байт и потом прочитать — эффективнее, чем размазывать их по десяткам кэш-строк.
S>>Ну класс если используешь Value типы тоже будут компактно расположены, а не валуе типы что на стеке, что не на нем все равно в куче
N>То есть в дотнет ещё не завезли escape analysis? (сомневаюсь)
S>>>>>>Конечно зависит от длительности задач, но если задачи непродолжительные, то пул потоков может и не переключаться а выполнять очередь заданий. N>>>>>Тоже не понимаю, при чём тут эта реплика. S>>>> В том, что с использованием пула потоков сводит к минимуму переключение потоков
N>>>Так не от желания конкретной задачи тут зависит, а от того, будет ли тот лок свободен. S>>Вот ссылочка из предыдущего сообщения AsyncSemaphore
S>>Если lock занят то возвращается Task и ожидается пока не вызовется SetResult S>>Никакой остановки потока нет. Да внутри конечно есть короткий lock который нужно заменять на SpinLock
N>Почему это "нужно заменять"? Обычный lock настолько неэффективен?
Обычный lock может быть долгим и ожидать его могут тысячи потоков.
Ну вот я и хочу разобраться если переключение потоков так эффективно, то нахрена городить async await?
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Sharov, Вы писали:
S>Здравствуйте, Serginio1, Вы писали:
S>>Ну вот я и хочу разобраться если переключение потоков так эффективно, то нахрена городить async await?
S>Для типизации тасков/промисов .
Тут речь про потоки и бесплатность их переключения. По сути раньше синхронный код и использованием ожиданием эвента при вызове асинхронной операции была нормальной практикой.
Но было сказано, что это отстой ибо есть большие затраты на переключение потока. Поэтому лучше использовать пул потока с минимумом переключений.
Но в этой ветке утверждают, что это все херня ибо все очень быстро и использование потоков и хэндлов не проблема и стоимость переключения минимальны.
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали: S>>Для типизации тасков/промисов .
S> Тут речь про потоки и бесплатность их переключения. По сути раньше синхронный код и использованием ожиданием эвента при вызове асинхронной операции была нормальной практикой. S>Но было сказано, что это отстой ибо есть большие затраты на переключение потока.
Вероятно это связано с многоядерностью ... Хотя не факт -- ведь в чем разница для async\await от ядерности?
Т.е. мы четко типизируем "выполни это после этого (io операции какой-нибудь)" и нам ядерыные объекты синхронизации
тут ни к чему.
S>Поэтому лучше использовать пул потока с минимумом переключений.
Пул потоков это про создание потоков, а не про переключение.
Здравствуйте, Serginio1, Вы писали:
N>>Так с точки зрения ОС что поможет тут осознать, когда можно переключать на другую нить вообще другого процесса, а когда лучше этого не делать, потому что захвачен важный лок (и даже в случае асинхронных локов это блокирует другие задачи)? N>>Я б ожидал каких-то явных системных вызовов, но тут про них ни слова. S>Ту тут главное tcs.TrySetResult(item); S>И фцфше может продолжаться в этом же потоке в зависимости от условий создания TaskCompletionSource
Как по мне, это мало того, что имеет очень слабое отношение к теме дискуссии, так и вообще какое-то кошмарное безумие. На чём тут дедлочиться??
Ну, стал родитель в ожидание. Пошёл запускаться код из вложенной задачи. Ну поспал (Thread.Sleep асинхронный?) Поставил результат. Родитель проснулся. Что может пойти не так? Что за бардак там в дотнете, или C#, кто это строит?
Я вот хотел сделать (на Core 5.0) чтобы коллбэки на чтение сокета срабатывали всегда в нужной нитке. Не получается: мне их, фактически, принудительно загоняют в какой-то пул, который я не просил создавать, и я должен ещё думать о мьютексах, чтобы применить их данные. А тут, наоборот, какая-то самоблокировка на одной нитке непонятно на чём.
И при чём тут какой-то TrySetResult? Try на ожидание — я понимаю, а на SetResult — нет. Мы заданием результата для Task снижаем "напряжение" в системе, сокращая количество ожидаемых сущностей и сокращая contention за ресурсы (включая ожидания), тогда какой нафиг Try?
N>>Кто сбрасывается? Кэш процессора? Зачем? Даже с мерами против Meltdown/Spectre кэш сбрасывается при переключении между процессами, а не между нитками одного процесса. Хотя о чём и, главное, чем думают тут в MS, я не в курсе.
S>Ну кэш то первого уровня точто сбросится ибо слишком мал. И все зависит от используемых данных потока.
Он не _сбросится_. Он _вымоется_. Разница принципиальна. И почему ты думаешь, что они будут использовать настолько разные код и данные?
Я вон выше уже упоминал: открыл сокет, повесил коллбэки на приём датаграмм. Послал две датаграммы. Один коллбэк вызвался в нитке 6, другой — в нитке 9 (дотнет нумерует). Зачем?
S>>>У меня вопрос если все так прекрасно с переключением потоков, то зачем делают все, что бы отказаться от потоков и использовать очередь потоков?
N>>Кто сказал, что "всё прекрасно"? Я не знаю, откуда и как вы делаете такие выводы. N>>И что это "все", что делают чтобы отказаться? В основном делают там, где просто неудобно/дорого по другим причинам возлагать это на ОС. S>Асинхронность и производительность S>Утверждают, что выхлоп есть
По сравнению с чем? Там сплошная Обстракция(tm) без конкретики.
Ну, повторюсь, на Windows треды дорогие (и создание, и переключение). Вероятно, по сравнению с этим, действительно, польза async-await очевидна.
N>>Да, есть цена на переключение, даже вход и выход из системного вызова чего-то стоит, и на Windows эта цена сильно выше, чем на Linux. Может, потому для Windows и стараются сократить эти переключения. В Linux я не вижу такого активного стремления, если где-то такое делают, то в рантаймах со сложной собственной логикой и полностью "управляемым" выполнением (Go, Erlang).
S> Ну сейчас практически все сервера в облаке на линуксе, но там же и asinc await используются на прополуюю ибо основная то работа это запрос к базе данных и прочие асинхронные операции
Возможно. А меряли результат по сравнению с другими подходами?
S>>>Если lock занят то возвращается Task и ожидается пока не вызовется SetResult S>>>Никакой остановки потока нет. Да внутри конечно есть короткий lock который нужно заменять на SpinLock
N>>Почему это "нужно заменять"? Обычный lock настолько неэффективен? S>Обычный lock может быть долгим и ожидать его могут тысячи потоков.
Эээ...
1. Если у кого-то на "обычном локе" тысячи ниток, то тут в первую очередь нужно менять весь дизайн (может быть, даже архитектуру, в обычном понимании).
Любые локи предназначены для малого количества конкурентов за их ресурс. Даже по количеству хартов — это уже много. До 4 — нормально. Если выше — надо думать разделять на разные локи. Если выше 8 — срочно думать.
2. Я уже описывал работу адаптивных мьютексов. Вначале, да, цикл сколько-то попыток захвата в стиле спинлока — чистым CAS (если нет видимой очереди). Если они не получились, тогда уже начинать более серьёзную машину — или через await, или через Lock() в ядре.
Но противопоставлять спинлоки мьютексам и говорить про "эффективность" spinlock по сравнению с мьютексом — это просто смешно.
Спинлок заведомо менее эффективен на длительных периодах и большом количестве конкурентов. Есть случаи, когда его использование неизбежно — когда шедулера более высокого порядка просто нет (в первую очередь это борьба в ядре за общие ресурсы между разными хартами). Но в остальных случаях его использовать — только ухудшать.
S>Ну вот я и хочу разобраться если переключение потоков так эффективно, то нахрена городить async await?
1. "Во-первых, это красиво" (tm). Линейное написание кода в разы проще цепочек коллбэков, неважно, в явном виде или на промисах.
Если программист не справляется без await, он будет писать линейно, но будут сплошные блокировки на синхронных операциях. Await позволяет использовать широкие ресурсы кодеров с реальным улучшением характеристик именно за счёт этого — меньше блокировок.
2. При высокой цене переключения между нитками внутреннее переключение (шедулером на границе awaitʼа) будет в разы дешевле переключения через ОС.
Здравствуйте, Serginio1, Вы писали:
S>Но было сказано, что это отстой ибо есть большие затраты на переключение потока. Поэтому лучше использовать пул потока с минимумом переключений. S> Но в этой ветке утверждают, что это все херня ибо все очень быстро и использование потоков и хэндлов не проблема и стоимость переключения минимальны.
Если это "утверждают" про меня, я не говорю, что "херня", "стоимость переключения минимальна" и т.п. — я, наоборот, утверждаю, что для Windows она весьма высока (сам не мерял, данные "из интернетов"), и использование всех этих фокусов там может быть очень оправдано. Но — описываемые тобой цели типа "не переключать пока держится лок" автоматически этим не реализуются, и могут быть вместо этого только ухудшены, если нет явной подсказки ядру.
Здравствуйте, Sharov, Вы писали:
S>Вероятно это связано с многоядерностью ... Хотя не факт -- ведь в чем разница для async\await от ядерности?
Внутреннему шедулеру придётся это учитывать, но и работать в сумме может быстрее.
S>>Поэтому лучше использовать пул потока с минимумом переключений.
S>Пул потоков это про создание потоков, а не про переключение.
Здравствуйте, netch80, Вы писали:
N>Эээ... N>1. Если у кого-то на "обычном локе" тысячи ниток, то тут в первую очередь нужно менять весь дизайн (может быть, даже архитектуру, в обычном понимании). N>Любые локи предназначены для малого количества конкурентов за их ресурс. Даже по количеству хартов — это уже много. До 4 — нормально. Если выше — надо думать разделять на разные локи. Если выше 8 — срочно думать. N>2. Я уже описывал работу адаптивных мьютексов. Вначале, да, цикл сколько-то попыток захвата в стиле спинлока — чистым CAS (если нет видимой очереди). Если они не получились, тогда уже начинать более серьёзную машину — или через await, или через Lock() в ядре. N>Но противопоставлять спинлоки мьютексам и говорить про "эффективность" spinlock по сравнению с мьютексом — это просто смешно. N>Спинлок заведомо менее эффективен на длительных периодах и большом количестве конкурентов. Есть случаи, когда его использование неизбежно — когда шедулера более высокого порядка просто нет (в первую очередь это борьба в ядре за общие ресурсы между разными хартами). Но в остальных случаях его использовать — только ухудшать.
S>>Ну вот я и хочу разобраться если переключение потоков так эффективно, то нахрена городить async await?
N>1. "Во-первых, это красиво" (tm). Линейное написание кода в разы проще цепочек коллбэков, неважно, в явном виде или на промисах. N>Если программист не справляется без await, он будет писать линейно, но будут сплошные блокировки на синхронных операциях. Await позволяет использовать широкие ресурсы кодеров с реальным улучшением характеристик именно за счёт этого — меньше блокировок.
+ N>2. При высокой цене переключения между нитками внутреннее переключение (шедулером на границе awaitʼа) будет в разы дешевле переключения через ОС.
Вот спасибо! Все таки дешевле!
Ну давай возьмем типичный сервер. БОльшая часть задач сводится к вызову асинхронным методов а именно запрос к базе данных (не локальной), запрос к сайту, к файлу итд.
Выделять для каждого запроса отдельный поток не имеет смысла (тут и затраты на создание потока или держать пул огромного размера).
Как правило эти задачи небольшие.
Отдельный поток стоит выделять для длительных задач LongRunning
То есть смысла в выделении потоках на задачу нет. Есть смысл использовать пул потоков. Что было кстати в ранних версиях вэб сервисов ASP.Net.
Тогда тоже были и асинхронные делегаты (BeginInvoke() и EndInvoke()) и ThreadPool. Но в том же сервисе вызов вэб метода был синхронным, то есть привязан к одному потоку. То есть внутри то можно было использовать калбеки и прочее, но поток морозился на эвенте
То есть например когда мы в базе данных что то активно меняем, и запросов на чтение как раз и будут ждать пока не закончится запись.
Конечно можно по максимуму съузить блокировки, но не всегда это возможно.
и солнце б утром не вставало, когда бы не было меня
N>Я вот хотел сделать (на Core 5.0) чтобы коллбэки на чтение сокета срабатывали всегда в нужной нитке. Не получается: мне их, фактически, принудительно загоняют в какой-то пул, который я не просил создавать, и я должен ещё думать о мьютексах, чтобы применить их данные. А тут, наоборот, какая-то самоблокировка на одной нитке непонятно на чём.