2016-06-17 4 views
2

Я понимаю, что поток может кэшировать значение и ignore changes made on another thread, но мне интересно об этом. Возможно ли, чтобы поток изменил кешированное значение, которое никогда не покидает его кеш и поэтому никогда не видимо для других потоков?Когда я могу гарантировать, что значение, измененное в одном потоке, видно на другие темы?

Например, может этот код печать «Флаг правда» потому что нить a никогда не видит изменений, что нить b делает на flag? (Я не могу сделать это сделать, но я не могу доказать, что это, или некоторые вариации этого, не будет.)

var flag = true; 
var a = new Thread(() => { 
    Thread.Sleep(200); 
    Console.WriteLine($"Flag is {flag}"); 
}); 
var b = new Thread(() => { 
    flag = false; 
    while (true) { 
     // Do something to avoid a memory barrier 
    } 
}); 

a.Start(); 
b.Start(); 

a.Join(); 

Я могу себе представить, что на резьбе bflag может кэшировать в Регистр CPU, где он затем устанавливается в false, и когда b входит в цикл while, он никогда не получает шанс (или никогда не обращает на себя внимания) записать значение flag обратно в память, поэтому a всегда видит flag как истинный.

Из генераторов барьера памяти, перечисленных в this answer, мне кажется, что это возможно в теории. Я прав? Я не смог продемонстрировать это на практике. Может ли кто-нибудь придумать пример, который делает?

+0

http://stackoverflow.com/questions/6581848/memory-barrier-generators –

+0

@MatthewWatson, спасибо, эта ссылка отвечает на вопрос, который я изначально задавал, но не то, что я пытаюсь выяснить. Я отредактировал вопрос, чтобы отразить это. Я действительно преследую ошибку, которая может быть объяснена тем, что я описываю выше, но я хочу знать, насколько я прав в своих рассуждениях и насколько это возможно на самом деле. – eoinmullan

+0

ОК, я снова открыл этот вопрос, теперь он был изменен. –

ответ

1

Возможно ли, чтобы поток изменил кешированное значение, которое никогда не покидает его кеш и поэтому никогда не видимо для других потоков?

Если мы говорим буквально о аппаратных кэшах, нам нужно поговорить о конкретных семействах процессоров. И если вы работаете (как кажется) на x86 (и x64), вам нужно знать, что эти процессоры на самом деле имеют гораздо более сильную модель памяти, чем требуется для .NET.В системах x86 кэши поддерживают согласованность, и поэтому никакие записи не могут быть проигнорированы другими процессорами.

Если мы говорим об оптимизации, в которой определенное место памяти было прочитано в регистре процессора, а затем , последующий, считанный из памяти, просто повторно использует регистр, то на стороне записи нет аналогичного аналога , Вы заметите, что всегда есть по крайней мере один, считанный из фактической ячейки памяти, прежде чем мы предположим, что ничто иное не меняет это место памяти, и поэтому мы можем повторно использовать регистр.

На стороне записи нам сказали нажать что-то в определенную ячейку памяти. Мы должны хотя бы надавить на это место один раз, и, скорее всего, это будет деоптимизация, чтобы всегда хранить ранее известное значение в этом месте (особенно, если наш поток никогда не читается из него) в отдельном регистре, чтобы иметь возможность выполнить сравнение и выполнить операцию записи.

+0

Это почти полностью отвечает на вопрос, но могу ли я что-то прояснить? «Мы должны хотя бы надавить на это место один раз» - это толкает часть операции записи? то есть это не то, что можно отложить? – eoinmullan

+0

@eoinmullan - да, я просто пытался избежать чрезмерной записи. Это не то, что задерживается. –

0

Я не совсем уверен, что это отвечает на ваш вопрос, но здесь идет.

Если вы запустите версию (не отлаживаете) версии следующего кода, она никогда не завершится, потому что waitForFlag() никогда не видит измененную версию flag.

Однако, если вы закомментируете одну из указанных строк, программа будет прекратить действие.

Похоже, что любой вызов внешней библиотеки в цикле while (flag) заставляет оптимизатор не кэшировать значение flag.

Также (конечно) изготовление flag volatile предотвратит такую ​​оптимизацию.

using System; 
using System.Threading; 
using System.Threading.Tasks; 

namespace Demo 
{ 
    class Program 
    { 
     void run() 
     { 
      Task.Factory.StartNew(resetFlagAfter1s); 
      var waiter = Task.Factory.StartNew(waitForFlag); 

      waiter.Wait(); 
      Console.WriteLine("Done"); 
     } 

     void resetFlagAfter1s() 
     { 
      Thread.Sleep(1000); 
      flag = false; 
     } 

     void waitForFlag() 
     { 
      int x = 0; 

      while (flag) 
      { 
       ++x; 
       // Uncommenting this line make this thread see the changed value of flag. 
       // Console.WriteLine("Spinning"); 
      } 
     } 

     // Uncommenting "volatile" makes waitForFlag() see the changed value of flag. 
     /*volatile*/ bool flag = true; 

     static void Main() 
     { 
      new Program().run(); 
     } 
    } 
} 
+1

Это иллюстрирует, что происходит в вопросе, с которым я первоначально связан, т. Е. Поток кэширует значение и впоследствии пропускает обновление до этого значения на другой поток. Мне интересно, можно ли увидеть вариацию этого, когда поток кэширует значение и изменяет его кешированное значение, но это изменение никогда не встречается в других потоках, независимо от того, не перечитывают ли они значение. – eoinmullan

0

Research Thread.MemoryBarrier, и вы станете золотым.

0

Чтобы из простой правильно сделать, чтобы легче ввернуть

  1. Используйте замки при чтении/записи
  2. Используйте функции на Interlocked класса
  3. использовать барьеры памяти