2013-07-03 2 views
2

Когда многопоточность я знаю, что мне нужно блокировать переменные, если вы делаете такие вещи, как добавление и элемент в список, или я получаю исключение перекрестного потока. Но нужно ли их блокировать при назначении переменных? Я не возражаю, если поток получает старый экземпляр переменной - я просто не хочу, чтобы это ошибка. Вот пример того, что я имею в виду:Нужно ли использовать блокировки, просто присваивая переменные?

public void Run() 
    { 
     var thread1 = new Thread(new ThreadStart(Test)); 
     var thread2 = new Thread(new ThreadStart(Test)); 
     thread1.Start(); 
     thread2.Start(); 
    } 

    private static int _test; 

    private void Test() 
    { 
     while (true) 
     { 
      _test += 1; 
     } 
    } 
+0

Что вы подразумеваете под "ошибкой"? – user7116

+5

'+ =' является * read * и * присваиванием * (неатомно вместе). – Paul

+0

Извините - я имел в виду читать и назначать. Да, это то, что я пытался продемонстрировать с помощью + = – JoeS

ответ

0

Выполнение кода должно дать вам ответ ... вместо while(true) записи for(i=1;1<1e6;i++), записать результат на экран и запустить его.

Вы увидите, что это не означает 2e6, а что-то около 1.2e6. Так что да, вам нужно запереть, если вы хотите выйти 2e6.

Не просто гипотеза, после этого всегда проверяйте и утверждайте.

+1

Проблема в том, что вы можете делать такие тесты и работать с ними, а затем запускать их на другой машине с более медленным (или просто другим) оборудованием и сбой. – Servy

+0

Это правда (я знаю по опыту), однако этот случай достаточно прост, чтобы продемонстрировать результат даже на простых архитектурах. – gjvdkamp

0

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

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

Я включил примеры с краткими описаниями, не волнуйтесь о том, как поток запускается d, что не имеет отношения

private static bool _continueLoop = true; 
private static readonly object _continueLoopLock = new object(); 

private static void StopLoop() 
{ 
    lock(_continueLoopLock) 
     _continueLoop = false; 
} 

private static void ThreadALoopWillGetStales() 
{ 
    while(_continueLoop) 
    { 
     //do stuff 
     //this is not guaranteed to end 
    } 
} 

private static void ThreadALoopEventuallyCorrect() 
{ 
    while(true) 
    { 
     bool doContinue; 

     lock(_continueLoopLock) 
      doContinue = _continueLoop; 

     if(!doContinue) 
      break; 

     //do stuff 
     //this will sometimes result in a stale value 
     //but will eventually be correct 
    } 
} 

private static void ThreadALoopAlwaysCorrect() 
{ 
    while(true) 
    { 
     bool doContinue; 

     lock(_continueLoopLock) 
      if(!_continueLoop) 
      break; 

     //do stuff 
     //this will always be correct 
    } 
} 

private static void ThreadALoopPossibleDeadlocked() 
{ 
    lock(_continueLoopLock) 
     while(_continueLoop) 
     { 
      //if you only modify "_continueLoop" 
      //after Acquiring "_continueLoopLock" 
      //this will cause a deadlock 
     } 
} 

private static void StartThreadALoop() 
{ 
    ThreadPool.QueueUserWorkItem ((o)=>{ThreadALoopWillGetStales();}); 
} 
private static void StartEndTheLoop() 
{ 
    ThreadPool.QueueUserWorkItem((o)=> 
    { 
     //do stuff 
     StopLoop(); 
    }); 
} 

public static void Main(string[] args) 
{ 
    StartThreadALoop(); 
    StartEndTheLoop(); 
} 

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

+0

Даже если вы синхронизируете, вы все равно можете получить устаревшую копию, для вашего примера вам нужно объявить '_continueLoop' как [' volatile'] (http://msdn.microsoft.com/en-us/library/x13ttww7%28v= vs.71% 29.aspx) (Фактически в вашем примере блокировка не указана, поскольку операции присвоения для bool's являются атомарными.) –

+0

@ScottChamberlain вам не нужно делать это во всех случаях – konkked

+0

Да, но в вашем случае блокировка не приносит никакой пользы, и вы не получаете «свежесть», которую вы требуете. –

5

Если вы всего лишь , назначивint, тогда нет. Но здесь вы не просто назначаете. Вы увеличиваетесь. Поэтому вам нужна какая-то синхронизация.

В вы хотите увеличить, используйте Interlocked.Increment:

Interlocked.Increment(ref _test); 
+0

Это также создает забор памяти или что-то, чтобы гарантировать, что значение '_test' не является устаревшим? В документации говорится, что это атомный, но я не думаю, что атомарное подразумевает синхронизацию. (Я не очень хорошо знаком с моделью памяти CLR.) – DaoWen

+0

@ DaoWen Да, это добавит соответствующие барьеры памяти. Атомный означает, что в любой момент времени вы можете только заметить, что действие либо произошло, либо нет. Неатомная операция означает, что можно наблюдать некоторое частично полное состояние, в котором он начал что-то делать, но не закончил. Одним из способов (но не единственным способом) достижения атомарности является синхронизация. – Servy

+0

@Servy. Что я имел в виду под термином «Я не думаю, что атомарное подразумевается синхронизированным» - это то, что вы можете атомизировать операцию, такую ​​как _increment_, к устаревшему значению, кэшируемому текущим потоком. Думаю, не было бы смысла предлагать библиотечную программу, которая бы делала что-то подобное. – DaoWen

Смежные вопросы