Наверно без экспериментов не обойтись…
Хочу сказать, что в своей практике я не применяю PriorityLock для долгих ожиданий и не получал просадки производительности из-за этого.
Если у вас есть желание, пожалуйста приведите пример кода, который будет правильнее чем этот:
var spin = new SpinWait();
while (Interlocked.CompareExchange(ref mgr.LowCount, 0, 0) != 0)
spin.SpinOnce();
По поводу NextSpinWillYield не соглашусь, написано использовать это для двухэтапной операции ожидания. У меня контекст использования SpinWait ограничен только ожиданием счетчика.
А вот про создание SpinWait внутри метода соглашусь, изучил исходники, где применяется SpinWait, действительно стоит делать так как вы сказали.
Прокомментируйте пожалуйста чем плохо держать SpinWait полем в классе? Сам не могу предложить аргументов против.
По поводу второго, согласно статье SpinWait настолько умен, что может работать как в быстром, так и в долгом ожидании. Но понятно, что чем короче операции внутри лока, тем лучше.
Как я понимаю KamushekORIGINAL говорит об ошибке платформы, получении lock convoy. Но думаю не стоит записывать это на нашу совесть. А если такое произошло, видимо стоит менять архитектуру на неблокирющие операции.
Приложение из примера автоматизирует пользовательские шаблоны, оно манипулирует большим количеством потоков и большим количеством тяжелых процессов (ограничено лишь ресурсами компьютера). Менеджер из примера обрабатывает задания, при этом должен держать несколько коллекций в синхронизированном состоянии.
На самом деле даже используя обычный lock это не было узким местом. PriorityLock появился для решения проблем с UI. Архитектурно задачу я решил позже, вынес UI в отдельный процесс, потому что понял что основные фризы интерфейса создавал GC.
Солгасен с вами, я тоже использую AsyncEx.
Но на NeoSmart.AsyncLock наткнулся при поиске переиспользования асинхронного лока. Ну и не мог не написать, что оно вообще не работает как lock.
Я немного о другом случае говорил: если внутри синхронного лока написать await — это гарантированнная проблема. Поэтому компилятор не дает внутри обычного lock писать await.
В исходниках SemaphoreSlim.WaitAsync использование lock находится в синхронной части кода, внутри lock там все детерминированно и максимально быстро выйдет из под него.
MonkAlex все правильно написал.
От себя лишь добавлю, что async/await — это механизм неблокирующих операций, но никто не отменяет необходимость критических секций в асинхронном коде. И использование синхронных методов синхронизации в асинхронном коде может плохо кончится, потому что Task != Thread, и зайдя в критическую секцию, таска может отпустить поток, а другая таска похватит поток и повторно зайдет в критическую секцию (в контексте использования PriorityLock, другие примитивы не проверял). Поэтому необходимы асинхронные методы синхронизации.
В ReadWriteLock сразу несколько читателей могут зайти внутрь критической секции. PriorityLock позволяет только одному потоку заходить в критическую секцию как обычный lock, но имеет два приоритета примерно как в ReadWriteLock.
Спасибо за критику. Да, наверно пример не удачный. Задержки по несколько секунд исчезли после написания PriorityLock, но все равно вы правы, приоритетные очереди уместнее.
Тем не менее я уверен каждый найдет для себя сценарии использования такого примитива, я например много где его применяю.
По поводу производительности, я раньше тоже думал что SpinOnce это нечто злое, но как показала практика, SpinOnce не нагружает процессор никак, при этом дает процессорное время другим потокам в нормальном порядке. К тому же в комбинации с семафорами, SpinOnce не вызывается во всех ожидающих потоках.
Да, на глаз я тоже вижу возможный рассинхрон. Но сколько тесты не гонял, не смог получить проблему. Поэтому залил такой вариант.
Хочу сказать, что в своей практике я не применяю PriorityLock для долгих ожиданий и не получал просадки производительности из-за этого.
Если у вас есть желание, пожалуйста приведите пример кода, который будет правильнее чем этот:
А вот про создание SpinWait внутри метода соглашусь, изучил исходники, где применяется SpinWait, действительно стоит делать так как вы сказали.
По поводу второго, согласно статье SpinWait настолько умен, что может работать как в быстром, так и в долгом ожидании. Но понятно, что чем короче операции внутри лока, тем лучше.
На самом деле даже используя обычный lock это не было узким местом. PriorityLock появился для решения проблем с UI. Архитектурно задачу я решил позже, вынес UI в отдельный процесс, потому что понял что основные фризы интерфейса создавал GC.
По поводу полезных расширений, я обожаю Custom Document Well из Productivity Power Tools.
Но на NeoSmart.AsyncLock наткнулся при поиске переиспользования асинхронного лока. Ну и не мог не написать, что оно вообще не работает как lock.
В исходниках SemaphoreSlim.WaitAsync использование lock находится в синхронной части кода, внутри lock там все детерминированно и максимально быстро выйдет из под него.
От себя лишь добавлю, что async/await — это механизм неблокирующих операций, но никто не отменяет необходимость критических секций в асинхронном коде. И использование синхронных методов синхронизации в асинхронном коде может плохо кончится, потому что Task != Thread, и зайдя в критическую секцию, таска может отпустить поток, а другая таска похватит поток и повторно зайдет в критическую секцию (в контексте использования PriorityLock, другие примитивы не проверял). Поэтому необходимы асинхронные методы синхронизации.
А что вас смущает в async lock?
Тем не менее я уверен каждый найдет для себя сценарии использования такого примитива, я например много где его применяю.
По поводу производительности, я раньше тоже думал что SpinOnce это нечто злое, но как показала практика, SpinOnce не нагружает процессор никак, при этом дает процессорное время другим потокам в нормальном порядке. К тому же в комбинации с семафорами, SpinOnce не вызывается во всех ожидающих потоках.