Пытается заменить lock() на SpinWait.SpinUntil(), но это не работает
Давайте начнем с кода;
checkedUnlock
является HashSet<ulong>
_hashsetLock
это объект
lock (_hashsetLock)
newMap = checkedUnlock.Add(uniqueId);
против
fun
в инт
SpinWait.SpinUntil(() => Interlocked.CompareExchange(ref fun, 1, 0) == 1);
newMap = checkedUnlock.Add(uniqueId);
fun = 0;
мое понимание SpinWait
в этом случае должен работать как lock()
но есть еще элементы, добавленные в HashSet
иногда это соответствует блокировке, иногда есть еще от 1 до 5 элементов, которые делают очевидным, что он не работает
мое понимание неверно?
редактировать
Я попробовал это, и, кажется, работает, мой тест показывает тот же номер, что и lock()
до сих пор
SpinWait spin = new SpinWait();
while (Interlocked.CompareExchange(ref fun, 1, 0) == 1)
spin.SpinOnce();
так почему бы это работать с этим, но не SpinWait.SpinUntil()
?
редактировать № 2
небольшое полное приложение, чтобы увидеть
в этом коде SpinWait.SpinUntil
когда-нибудь взорвется (добавление сгенерирует исключение), но когда это сработает, количество будет другим, поэтому мое ожидаемое поведение для этого неверно
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var list = new List<int>();
var rnd = new Random(42);
for (var i = 0; i < 1000000; ++i)
list.Add(rnd.Next(500000));
object _lock1 = new object();
var hashset1 = new HashSet<int>();
int _lock2 = 0;
var hashset2 = new HashSet<int>();
int _lock3 = 0;
var hashset3 = new HashSet<int>();
Parallel.ForEach(list, item =>
{
/******************/
lock (_lock1)
hashset1.Add(item);
/******************/
/******************/
SpinWait.SpinUntil(() => Interlocked.CompareExchange(ref _lock2, 1, 0) == 1);
hashset2.Add(item);
_lock2 = 0;
/******************/
/******************/
SpinWait spin = new SpinWait();
while (Interlocked.CompareExchange(ref _lock3, 1, 0) == 1)
spin.SpinOnce();
hashset3.Add(item);
_lock3 = 0;
/******************/
});
Console.WriteLine("Lock: {0}", hashset1.Count);
Console.WriteLine("SpinWaitUntil: {0}", hashset2.Count);
Console.WriteLine("SpinWait: {0}", hashset3.Count);
Console.ReadKey();
}
}
}
1 ответ
Условие, используемое в SpinWait.SpinUntil, неверно.
- Interlocked.CompareExchange возвращает исходное значение переменной.
- MSDN документы SpinWait.SpinUntil говорит, условие
Делегат должен выполняться снова и снова, пока он не вернет true.
Вы хотите вращаться, пока не произойдет переход 0 -> 1, поэтому условие должно быть
Interlocked.CompareExchange(ref fun, 1, 0) == 0
Последующие вызовы CompareExchange в других потоках приводят к 1, поэтому они будут ждать, пока fun
флаг восстанавливается до 0 потоком "победителя".
Некоторые дальнейшие замечания:
fun = 0;
должен работать на архитектуре x86, но я не уверен, что это везде правильно. Если вы используете Interlocked для доступа к полю, лучше всего использовать Interlocked для доступа к этому полю. Поэтому я предлагаюInterlocked.Exchange(ref fun, 0)
вместо.- SpinWait редко является хорошим решением с точки зрения производительности, поскольку он не позволяет операционной системе переводить вращающийся поток в состояние ожидания. Он должен использоваться только для очень коротких ожиданий. ( Пример правильного использования). Простые блокировки (также известные как Monitor.Enter/Exit) или SemaphoreSlim будут делать вообще, или вы можете рассмотреть ReaderWriterLockSlim, если число операций чтения >> число операций записи.