Здравствуйте, vdimas, Вы писали:
V>Здравствуйте, artelk, Вы писали:
V>>>Отличия от FIFO чаще являются светошумовыми эффектами разных приоритетов, например, при инверсии приоритетов, когда низкоприоритетному потоку дают больше квантов времени, чем его соседям по приоритету, чтобы он освободил ресурс для высокоприоритетного потока.
A>>Не понял причем тут очередь потоков.
V>А очередь каких сущностей ожидает готовности традиционного семафора?
Очередь продолжений, которые нужно выполнить при вызовах Release. Мы ведь асинхронную блокировку обсуждаем?
V>>>В общем, навскидку сложно сказать, насколько это будет дешевле обычной сериализации доступа через мьютекс (критическую секцию) к состоянию асинхронного семафора, ведь в случае оперирования более легковесными чем Task объектами эта блокировка будут весьма короткой, т.е. вероятность столкновения на ней будет мала.
A>>Это ты об операции добавления в очередь пишешь? Не понятно как к этому относится тяжеловесность\легковесность объектов Task или их аналогов.
V>Этот абзац отвечал на предложение о lock-free реализации очереди.
Ну вот создал ты Task (или аналог) за пределами CAS-цикла или блокировки, а внутри вставляешь его в очередь. Каким образом тяжеловесность Task влияет на время блокировки?
V>>>Я бы ограничился одним массивом.
V>>>В реальной жизни конкурентность к ресурсу обычно невысока — от силы десятки или единицы сотен "заявок".
A>>Как одним? Давая обясню проблему, которую я имел ввиду. Вот вызвал клиент WaitAsync и получил назад MyValueTask. Далее он может его сохранить где-то у себя и проверить у него свойство IsCompleted через час.
V>IsCompleted не является обязательным для реализации своих awaiter-ов.
V>...
V>Соответственно, семафору или возвращаемой прокси-структуре (при вызове ConfigureAwait(bool) или вызове блокировки с таймаутом) можно будет делать только await.
С IsCompleted await работает примерно так:
if(myAwaiter.IsCompleted)
{
var r = myAwaiter.GetResult();
// выполнить продолжение по месту, без косвенности через делегат
}
else
{
//...
myAwaiter.OnComplete(action); // в action то же продолжение
}
В случае, когда awaiter completed это позволяет избежать косвенности вызова через делегат и часто избежать инстанцирования самого делегата (в куче). Мы ведь такты оптимизируем?
В любом случае между вызовом WaitAsync и вызовом OnComplete может быть произвольная задержка, так что вопрос как переиспользовать ячейки массива остается открытим.
A>>И значение должно быть верным. Т.е. элемент массива должен быть прибит гвоздями к своему "владельцу" MyValueTask и не может быть переиспользован даже если последний давно стал Complete.
V>Если настолько заморачиваться, то массивы стоит уже выбирать из глобального пула массивов с соответствующими столкновениями на этом синхронизируемом пуле.
Тогда нужен API для явного возврата MyValueTask в пул и клиентский код обязан это делать.