Сообщение Re[20]: .net core и async lock от 16.04.2021 17:45
Изменено 19.04.2021 17:29 vdimas
Re[20]: .net core и async lock
Здравствуйте, artelk, Вы писали:
V>>>>Отличия от FIFO чаще являются светошумовыми эффектами разных приоритетов, например, при инверсии приоритетов, когда низкоприоритетному потоку дают больше квантов времени, чем его соседям по приоритету, чтобы он освободил ресурс для высокоприоритетного потока.
A>>>Не понял причем тут очередь потоков.
V>>А очередь каких сущностей ожидает готовности традиционного семафора?
A>Очередь продолжений, которые нужно выполнить при вызовах Release. Мы ведь асинхронную блокировку обсуждаем?
Потоки OS у них называют нитями (thread), а вот эти асинхронные — потоками (flow).
По-русски оба просто потоки.
V>>>>В общем, навскидку сложно сказать, насколько это будет дешевле обычной сериализации доступа через мьютекс (критическую секцию) к состоянию асинхронного семафора, ведь в случае оперирования более легковесными чем Task объектами эта блокировка будут весьма короткой, т.е. вероятность столкновения на ней будет мала.
A>>>Это ты об операции добавления в очередь пишешь? Не понятно как к этому относится тяжеловесность\легковесность объектов Task или их аналогов.
V>>Этот абзац отвечал на предложение о lock-free реализации очереди.
A>Ну вот создал ты Task (или аналог) за пределами CAS-цикла или блокировки, а внутри вставляешь его в очередь.
Или не вставляешь, у нас ведь lock-free, т.е. крутишься в некоем цикле с проверками, вдруг на очередном цикле семафор освободится?
Тогда тяжеловесная задача создана в предыдущих циклах зря.
V>>IsCompleted не является обязательным для реализации своих awaiter-ов.
A>С IsCompleted await работает примерно так:
A>
A>В любом случае между вызовом WaitAsync и вызовом OnComplete может быть произвольная задержка, так что вопрос как переиспользовать ячейки массива остается открытим.
Тогда зачем тебе эта задержка, если она тебя так напрягает? ))
Пусть _sem.GetAwaiter() не захватывает ресурс.
Пусть ресурс захватывается только в момент вызова OnCompleted(action).
V>>Если настолько заморачиваться, то массивы стоит уже выбирать из глобального пула массивов с соответствующими столкновениями на этом синхронизируемом пуле.
A>Тогда нужен API для явного возврата MyValueTask в пул и клиентский код обязан это делать.
Прям-таки "обязан"? ))
V>>>>Отличия от FIFO чаще являются светошумовыми эффектами разных приоритетов, например, при инверсии приоритетов, когда низкоприоритетному потоку дают больше квантов времени, чем его соседям по приоритету, чтобы он освободил ресурс для высокоприоритетного потока.
A>>>Не понял причем тут очередь потоков.
V>>А очередь каких сущностей ожидает готовности традиционного семафора?
A>Очередь продолжений, которые нужно выполнить при вызовах Release. Мы ведь асинхронную блокировку обсуждаем?
Потоки OS у них называют нитями (thread), а вот эти асинхронные — потоками (flow).
По-русски оба просто потоки.
V>>>>В общем, навскидку сложно сказать, насколько это будет дешевле обычной сериализации доступа через мьютекс (критическую секцию) к состоянию асинхронного семафора, ведь в случае оперирования более легковесными чем Task объектами эта блокировка будут весьма короткой, т.е. вероятность столкновения на ней будет мала.
A>>>Это ты об операции добавления в очередь пишешь? Не понятно как к этому относится тяжеловесность\легковесность объектов Task или их аналогов.
V>>Этот абзац отвечал на предложение о lock-free реализации очереди.
A>Ну вот создал ты Task (или аналог) за пределами CAS-цикла или блокировки, а внутри вставляешь его в очередь.
Или не вставляешь, у нас ведь lock-free, т.е. крутишься в некоем цикле с проверками, вдруг на очередном цикле семафор освободится?
Тогда тяжеловесная задача создана в предыдущих циклах зря.
V>>IsCompleted не является обязательным для реализации своих awaiter-ов.
A>С IsCompleted await работает примерно так:
A>
A>if(myAwaiter.IsCompleted)
A>{
A> var r = myAwaiter.GetResult();
A> // выполнить продолжение по месту, без косвенности через делегат
A>}
A>else
A>{
A> //...
A> myAwaiter.OnComplete(action); // в action то же продолжение
A>}
И какие проблемы?
Если охота иметь дополнительную семантику - попробовать захватить ресурс без постановки продолжения в очередь, то что мешает выразить эту надобность через дополнительную сигнатуру, например:
SyncGuard TryAcquire();
где SyncGuard является, например, ref struct, имеет public void Dispose() и оператор приведения к bool.
В твоей версии awaiter должен быть disposable, а этого хотелось бы избежать, бо disposable должны только гарды, например, возвращаемый через await некий AsyncGuard.
A>В случае, когда awaiter completed это позволяет избежать косвенности вызова через делегат и часто избежать инстанцирования самого делегата (в куче). Мы ведь такты оптимизируем?
И такты, и "синтаксический оверхед". (С)
В твоей версии надо явно вызывать GetAwaiter(), а потом еще наверчивать конструкции сверху, чтобы освободить семафор автоматом через using.
Если разложить разные сценарии по разным сигнатурам, то будет примерно так:
[cs]
using var acquired = _sema.TryAcquire();
if(acquired) {
// do smth sync
return;
}
using(await _sema) {
// do smth async
}
A>В любом случае между вызовом WaitAsync и вызовом OnComplete может быть произвольная задержка, так что вопрос как переиспользовать ячейки массива остается открытим.
Тогда зачем тебе эта задержка, если она тебя так напрягает? ))
Пусть _sem.GetAwaiter() не захватывает ресурс.
Пусть ресурс захватывается только в момент вызова OnCompleted(action).
V>>Если настолько заморачиваться, то массивы стоит уже выбирать из глобального пула массивов с соответствующими столкновениями на этом синхронизируемом пуле.
A>Тогда нужен API для явного возврата MyValueTask в пул и клиентский код обязан это делать.
Прям-таки "обязан"? ))
Re[20]: .net core и async lock
Здравствуйте, artelk, Вы писали:
V>>>>Отличия от FIFO чаще являются светошумовыми эффектами разных приоритетов, например, при инверсии приоритетов, когда низкоприоритетному потоку дают больше квантов времени, чем его соседям по приоритету, чтобы он освободил ресурс для высокоприоритетного потока.
A>>>Не понял причем тут очередь потоков.
V>>А очередь каких сущностей ожидает готовности традиционного семафора?
A>Очередь продолжений, которые нужно выполнить при вызовах Release. Мы ведь асинхронную блокировку обсуждаем?
Потоки OS у них называют нитями (thread), а вот эти асинхронные — потоками (flow).
По-русски оба просто потоки.
V>>>>В общем, навскидку сложно сказать, насколько это будет дешевле обычной сериализации доступа через мьютекс (критическую секцию) к состоянию асинхронного семафора, ведь в случае оперирования более легковесными чем Task объектами эта блокировка будут весьма короткой, т.е. вероятность столкновения на ней будет мала.
A>>>Это ты об операции добавления в очередь пишешь? Не понятно как к этому относится тяжеловесность\легковесность объектов Task или их аналогов.
V>>Этот абзац отвечал на предложение о lock-free реализации очереди.
A>Ну вот создал ты Task (или аналог) за пределами CAS-цикла или блокировки, а внутри вставляешь его в очередь.
Или не вставляешь, у нас ведь lock-free, т.е. крутишься в некоем цикле с проверками, вдруг на очередном цикле семафор освободится?
Тогда тяжеловесная задача создана в предыдущих циклах зря.
V>>IsCompleted не является обязательным для реализации своих awaiter-ов.
A>С IsCompleted await работает примерно так:
A>
И какие проблемы?
Если охота иметь дополнительную семантику — попробовать захватить ресурс без постановки продолжения в очередь, то что мешает выразить эту надобность через дополнительную сигнатуру, например:
SyncGuard TryAcquire();
где SyncGuard является, например, ref struct, имеет public void Dispose() и оператор приведения к bool.
В твоей версии awaiter должен быть disposable, а этого хотелось бы избежать, бо disposable должны только гарды, например, возвращаемый через await некий AsyncGuard.
A>В случае, когда awaiter completed это позволяет избежать косвенности вызова через делегат и часто избежать инстанцирования самого делегата (в куче). Мы ведь такты оптимизируем?
И такты, и "синтаксический оверхед". (С)
В твоей версии надо явно вызывать GetAwaiter(), а потом еще наверчивать конструкции сверху, чтобы освободить семафор автоматом через using.
Если разложить разные сценарии по разным сигнатурам, то будет примерно так:
A>В любом случае между вызовом WaitAsync и вызовом OnComplete может быть произвольная задержка, так что вопрос как переиспользовать ячейки массива остается открытим.
Тогда зачем тебе эта задержка, если она тебя так напрягает? ))
Пусть _sem.GetAwaiter() не захватывает ресурс.
Пусть ресурс захватывается только в момент вызова OnCompleted(action).
V>>Если настолько заморачиваться, то массивы стоит уже выбирать из глобального пула массивов с соответствующими столкновениями на этом синхронизируемом пуле.
A>Тогда нужен API для явного возврата MyValueTask в пул и клиентский код обязан это делать.
Прям-таки "обязан"? ))
V>>>>Отличия от FIFO чаще являются светошумовыми эффектами разных приоритетов, например, при инверсии приоритетов, когда низкоприоритетному потоку дают больше квантов времени, чем его соседям по приоритету, чтобы он освободил ресурс для высокоприоритетного потока.
A>>>Не понял причем тут очередь потоков.
V>>А очередь каких сущностей ожидает готовности традиционного семафора?
A>Очередь продолжений, которые нужно выполнить при вызовах Release. Мы ведь асинхронную блокировку обсуждаем?
Потоки OS у них называют нитями (thread), а вот эти асинхронные — потоками (flow).
По-русски оба просто потоки.
V>>>>В общем, навскидку сложно сказать, насколько это будет дешевле обычной сериализации доступа через мьютекс (критическую секцию) к состоянию асинхронного семафора, ведь в случае оперирования более легковесными чем Task объектами эта блокировка будут весьма короткой, т.е. вероятность столкновения на ней будет мала.
A>>>Это ты об операции добавления в очередь пишешь? Не понятно как к этому относится тяжеловесность\легковесность объектов Task или их аналогов.
V>>Этот абзац отвечал на предложение о lock-free реализации очереди.
A>Ну вот создал ты Task (или аналог) за пределами CAS-цикла или блокировки, а внутри вставляешь его в очередь.
Или не вставляешь, у нас ведь lock-free, т.е. крутишься в некоем цикле с проверками, вдруг на очередном цикле семафор освободится?
Тогда тяжеловесная задача создана в предыдущих циклах зря.
V>>IsCompleted не является обязательным для реализации своих awaiter-ов.
A>С IsCompleted await работает примерно так:
A>
A>if(myAwaiter.IsCompleted)
A>{
A> var r = myAwaiter.GetResult();
A> // выполнить продолжение по месту, без косвенности через делегат
A>}
A>else
A>{
A> //...
A> myAwaiter.OnComplete(action); // в action то же продолжение
A>}
A>
И какие проблемы?
Если охота иметь дополнительную семантику — попробовать захватить ресурс без постановки продолжения в очередь, то что мешает выразить эту надобность через дополнительную сигнатуру, например:
SyncGuard TryAcquire();
где SyncGuard является, например, ref struct, имеет public void Dispose() и оператор приведения к bool.
В твоей версии awaiter должен быть disposable, а этого хотелось бы избежать, бо disposable должны только гарды, например, возвращаемый через await некий AsyncGuard.
A>В случае, когда awaiter completed это позволяет избежать косвенности вызова через делегат и часто избежать инстанцирования самого делегата (в куче). Мы ведь такты оптимизируем?
И такты, и "синтаксический оверхед". (С)
В твоей версии надо явно вызывать GetAwaiter(), а потом еще наверчивать конструкции сверху, чтобы освободить семафор автоматом через using.
Если разложить разные сценарии по разным сигнатурам, то будет примерно так:
using var acquired = _sema.TryAcquire();
if(acquired) {
// do smth sync
return;
}
using(await _sema) {
// do smth async
}
A>В любом случае между вызовом WaitAsync и вызовом OnComplete может быть произвольная задержка, так что вопрос как переиспользовать ячейки массива остается открытим.
Тогда зачем тебе эта задержка, если она тебя так напрягает? ))
Пусть _sem.GetAwaiter() не захватывает ресурс.
Пусть ресурс захватывается только в момент вызова OnCompleted(action).
V>>Если настолько заморачиваться, то массивы стоит уже выбирать из глобального пула массивов с соответствующими столкновениями на этом синхронизируемом пуле.
A>Тогда нужен API для явного возврата MyValueTask в пул и клиентский код обязан это делать.
Прям-таки "обязан"? ))