Да, я знаю, что на следующий вопрос можно было бы ответить с помощью «Вместо этого используйте ключевое слово блокировки» или чего-то подобного. Но так как это просто "для развлечения", меня это не волнует.
Я сделал простую блокировку, используя атомарные операции:
public class LowLock
{
volatile int locked = 0;
public void Enter(Action action)
{
var s = new SpinWait();
while (true)
{
var inLock = locked; // release-fence (read)
// If CompareExchange equals 1, we won the race.
if (Interlocked.CompareExchange(ref locked, 1, inLock) == 1)
{
action();
locked = 0; // acquire fence (write)
break; // exit the while loop
}
else s.SpinOnce(); // lost the race. Spin and try again.
}
}
}
Я использую приведенную выше блокировку в простом цикле for, который добавляет строку к обычному List
с помощью цель сделать метод add
потокобезопасным, когда он заключен в метод Enter
из LowLock
.
Код выглядит так:
static void Main(string[] args)
{
var numbers = new List<int>();
var sw = Stopwatch.StartNew();
var cd = new CountdownEvent(10000);
for (int i = 0; i < 10000; i++)
{
ThreadPool.QueueUserWorkItem(o =>
{
low.Enter(() => numbers.Add(i));
cd.Signal();
});
}
cd.Wait();
sw.Stop();
Console.WriteLine("Time = {0} | results = {1}", sw.ElapsedMilliseconds, numbers.Count);
Console.ReadKey();
}
Теперь сложная часть состоит в том, что когда основной поток попадает в Console.WriteLine
, который печатает время и количество элементов в списке, количество элементов должно быть равным к счетчику, заданному для CountdownEvent
(10000) - он работает большую часть времени, но иногда в списке всего 9983 элемента, иногда 9993. Что я упускаю из виду?