Потоково-безопасное использование помощников блокировки (относительно барьеров памяти) - PullRequest
5 голосов
/ 04 июля 2011

Помощниками блокировки я имею в виду одноразовые объекты, с помощью которых можно реализовать блокировку с помощью операторов using.Например, рассмотрим типичное использование класса SyncLock из MiscUtil Джона Скита :

public class Example
{
    private readonly SyncLock _padlock;

    public Example()
    {
        _padlock = new SyncLock();
    }

    public void ConcurrentMethod()
    {
        using (_padlock.Lock())
        {
            // Now own the padlock - do concurrent stuff
        }
    }
}

Теперь рассмотрим следующее использование:

var example = new Example();
new Thread(example.ConcurrentMethod).Start();

Myвопрос заключается в следующем: поскольку example создается в одном потоке, а ConcurrentMethod вызывается в другом, поток ConcurrentMethod не может не обращать внимания на присваивание _padock в конструкторе (из-за кэширования / чтения потока-записать переупорядочение) и, таким образом, выбросить NullReferenceException (на _padLock)?

Я знаю, что блокировка с помощью Monitor / lock имеет преимущество в виде барьеров памяти, но при использовании помощников блокировкиЯ не понимаю, почему такие барьеры гарантированы.В этом случае, насколько я понимаю, конструктор должен быть изменен:

public Example()
{
    _padlock = new SyncLock();
    Thread.MemoryBarrier();
}

Источник: Понимание влияния методов низкого уровня блокировки в многопоточных приложениях

EDIT Ганс Пассант предполагает, что создание потока подразумевает барьер памяти.А как насчет:

var example = new Example();
ThreadPool.QueueUserWorkItem(s => example.ConcurrentMethod());

Теперь поток не обязательно создан ...

1 Ответ

10 голосов
/ 04 июля 2011

Нет, вам не нужно делать ничего особенного, чтобы гарантировать создание барьеров памяти. Это связано с тем, что практически любой механизм, используемый для получения метода, выполняемого в другом потоке, создает барьер release-fence в вызывающем потоке и барьер aquire-fence в рабочем потоке (на самом деле они могут быть полные ограждения). Таким образом, либо QueueUserWorkItem, либо Thread.Start автоматически вставит необходимые барьеры. Ваш код в безопасности.

Кроме того, из-за тангенциального интереса Thread.Sleep также создает барьер памяти. Это интересно, потому что некоторые люди наивно используют Thread.Sleep для имитации чередования потоков. Если бы эта стратегия использовалась для устранения неполадок кода с низким уровнем блокировки, то она вполне могла бы замаскировать проблему, которую вы пытались найти.

...