Re[2]: Interlocked
От: RushDevion Россия  
Дата: 28.12.18 10:07
Оценка: 6 (2) +1
Здравствуйте, RAza, Вы писали:

S>>А чем, кстати, Interlocked не подошел?

RD>>3. И да, можно переписать на Interlocked.CompareExchange и обойтись без lock.
RA>Никогда не использовал это на практике. Вас не затруднит показать реализацию? И в двух словах объяснить в чем плюсы по сравнению с DCL?

Для начала, зачем все это надо.
1. Иногда код алгоритма на Interlocked-инструкциях получается чище, чем с применением примитивов синхронизации (lock, производные WaitHandle и т.п.)
2. Мы экономим объект (тот, на котором делается lock).
3. Interlocked-инструкции быстрее (ns против ms у других примитивов). Поэтому в высококонкурентных lock-free алгоритмах используют именно их.

И сразу оговорюсь, что рядовому разработчику корпоративного софта (ну типа меня ) исчезающе редко (т.е. практически никогда)
приходится писать код, где разница в производительности между Interlocked/не-Interlocked ощутимо влияет на performance.

Теперь по реализации. Покажу на примере getter'a для Value
public class Lazy<T> where T : class
{
    private readonly Func<T> m_Factory;
    private int m_Initializing = 0;
    private T m_Value;

    public Lazy(Func<T> factory) { m_Factory = factory; }

    public T Value
    {
        get
        {
            var curVal = Volatile.Read(ref m_Value);
            if (curVal != default(T)) return curVal;
        
            // Самый простой и красивый вариант. 
            // Когда я предлагал Interlocked, то думал именно о нем.
            // Атомарно проверяем, что m_Value == null, если да - инициализируем.
            // К сожалениею, он не обеспечивает требование exact-once инициализации.
            Interlocked.CompareExchange(ref m_Value, m_Factory(), default(T));
            return Volatile.Read(ref m_Value);


            // Вариант с exact-once инициализацией (без обработки ошибок)
            if (Interlocked.CompareExchange(ref m_Initializing, 1, 0) == 0)
            {
                // Захватили право на инициализацию, инициируем
                var val = m_Factory();
                Volatile.Write(ref m_Value, val);
                return val;
            }

            // Если мы попали сюда, то право на инициализацию захватил кто-то другой, будем ждать, пока он проинициализирует
            while (true)
            {
                var val = Volatile.Read(ref m_Value);
                if (val != default(T)) return val;

                // Это так называемый Sleep-wait, 
                // В lock-free часто применяют SpinWait/Thread.Yield либо их комбинации
                Thread.Sleep(TimeSpan.FromMilliseconds(5));
            }
        }
    }
}


А если добавить обработку ошибок инициализации, то код будет еще сложнее. При этом профит от lock-free выглядит более чем сомнительным.
Так что рассматривай это скорее как академический пример.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.