Re[2]: Доступ к локальной переменной из разных потоков
От: Aquilaware  
Дата: 17.11.20 23:50
Оценка: 245 (10) +1
Здравствуйте, VladD2, Вы писали:

VD>Дотнет не гарантирует атомарности и корректного разделение переменных и полей, но поле типа bool и целые, на практике, работают атомарно и корректно.


Дотнет гарантирует атомарность всех примитивных типов у которых sizeof(T) <= IntPtr.Size. Это прописано в спецификации .NET Memory Model.

VD>Проблемы могут начаться, если нужна точная и быстрая реакция на изменение значения. Какое-то время разные процессоры будут иметь свои копии. И изменение переменной в одном потоке не сразу попадет в кэш другого. Но в твоем примере это не важно. Ну, выйдет второй поток не через 5 секунд, а через 5 целых и 1 сотую секунды.


Для x86 синхронизация между кешами данных происходит при: 1) Memory Barrier 2) автоматически каждые 10 нс

Не все архитектуры CPU делают автоматическую синхронизацию кешей данных. Поэтому правильное решение состоит в указании ключевого слова volatile для переменной.

Но поскольку указать volatile для переменной обьявленной "локально" нельзя с точки зрения синтаксиса, нужно использовать методы Volatile.Read и Volatile.Write.

Сначала продемонстрирую вырожденный случай:

static void Main()
{
    bool b = true;

    new Thread(
        () =>
        {
            Thread.Sleep(1000);
            b = false;
        })
    {
        IsBackground = true
    }
    .Start();

    while (b)
    {
        //Console.WriteLine("b=" + b);
        //Thread.Sleep(100);
    }

    Console.WriteLine("b=" + b);
}


Этот пример зависает навсегда если собрать его в Release конфигурации под .NET 5.0.

Обратите внимание, что я закоментировал Console.WriteLine и Thread.Sleep. Почему? Потому что внутри эти операции используют примитивы синхронизации ОС, а они подразумевают неявный memory barrier. Например, документация Windows это декларирует явно.

Теперь заставим этот пример работать как надо. Например, так:

static void Main()
{
    bool b = true;

    new Thread(
        () =>
        {
            Thread.Sleep(1000);
            Volatile.Write(ref b, false);
        })
    {
        IsBackground = true
    }
    .Start();

    while (Volatile.Read(ref b))
    {
        //Console.WriteLine("b=" + b);
        //Thread.Sleep(100);
    }

    Console.WriteLine("b=" + b);
}


Или так:

static void Main()
{
    bool b = true;

    new Thread(
        () =>
        {
            Thread.Sleep(1000);

            Thread.MemoryBarrier();
            b = false;
        })
    {
        IsBackground = true
    }
    .Start();

    while (b)
    {
        Thread.MemoryBarrier();

        //Console.WriteLine("b=" + b);
        //Thread.Sleep(100);
    }

    Console.WriteLine("b=" + b);
}
Отредактировано 17.11.2020 23:53 Aquilaware . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.