Здравствуйте, 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Автор: Serginio1
Дата: 16.06.21
S>>> https://stackoverflow.com/questions/66387225/awaiting-a-single-net-event-with-a-valuetask
N>>Я что-то не могу это раскурить. Какой смысл в его применении?
S> Смысл в том, что замена lock на lockAsync. То есть нет никакого ожидания потока
Если оно сделано умно (с попытками захвата до переключения) — отлично.
S>>>Конечно зависит от длительности задач, но если задачи непродолжительные, то пул потоков может и не переключаться а выполнять очередь заданий.
N>>Тоже не понимаю, при чём тут эта реплика.
S> В том, что с использованием пула потоков сводит к минимуму переключение потоков
Так не от желания конкретной задачи тут зависит, а от того, будет ли тот лок свободен.