2015-12-18 3 views
-4

У меня возникла проблема с моей критической секцией Lock(). Ситуация в том, что мой вывод содержит дубликаты, я не могу этого сделать! Ниже вы можете увидеть код, который повторяет мою ситуацию. Я действительно тупой, основанный по моей ситуации, извините, если мне не хватает чего-то действительно глупого ...Блокировка не останавливается Дубликаты

private static object theLock = new object(); 
private static int currentNumber = 0; 

. . .

static void Main(string[] args) 
{ 
    for (int i = 0; i < 30; i++) 
     CreateAndRunWorker(); 
} 

. . .

private static void CreateAndRunWorker() 
{ 
    BackgroundWorker worker = new BackgroundWorker(); 
    worker.DoWork += new DoWorkEventHandler(TheWorkToBeDone); 
    worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(TheWorkAfterWork); 
    worker.RunWorkerAsync(); 
} 

. . .

private static void TheWorkToBeDone(object sender, DoWorkEventArgs e) 
{ 
    OutputNum(); 
} 

. . .

private static void TheWorkAfterWork(object sender, RunWorkerCompletedEventArgs e) 
{ 
    CreateAndRunWorker(); 
} 

. . .

private static void OutputNum() 
{ 
    lock (theLock) 
    { 
     currentNumber++; 
     Console.WriteLine(currentNumber); 
    } 
} 

Вышеупомянутый вызывается 25 работниками фона из функции Main(). Блокировка и счетчик инициализируются глобально. Вывод содержит повторяющиеся числа. Как?

+2

Покажите нам больше кода. Это в winforms? Как и где вы называете «OutputNum»? – dotctor

+2

Я бы сказал, что вы, вероятно, переустанавливаете «TheLock» где-то. В любом случае код, который вы нам показали, не содержит проблемы. –

+0

Я добавлю еще код, в течение одной секунды. Это консольное приложение. –

ответ

-1

Возможно, вы столкнулись с некоторыми эффектами кеша для локальных кешей на несвязанных ядрах. Проблема заключается в том, что сам int не объявлен или не обновлен таким образом, чтобы обеспечить постоянный локальный кеш. Вы могли бы решить эту проблему в пару способов:

Закрепление вашу реализацию

private static readonly object theLock = new object(); 
private static volatile int currentNumber = 0; // note the addition of "volatile" 

private static void OutputNum() 
{ 
    lock (theLock) 
    { 
     currentNumber++; 
     Console.WriteLine(currentNumber); 
    } 
} 

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

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

Избавление от замка

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

private static volatile int currentNumber = 0; 

private static void OutputNum() 
{ 
    int localRef = Interlocked.Increment(ref currentNumber); 
    Console.WriteLine(localRef); 
} 

Это снимает блокировку полностью, сохраняет решение более производительным, так как там меньше блокировок, но сохраняет все безопасность.

ВАЖНО: Используйте значение, возвращенное из блокировки.Приращение для будущей работы в том же методе, поскольку это не изменится из другого потока, обращаясь к одному и тому же коду, и изменит значение до того, как может выполнить Console.WriteLine().

Понимание, почему это работает

  1. Среда CLR выполняет оптимизацию и перекомпиляции кода во время выполнения. Ключевое слово volatile гарантирует, что оптимизация не включает доступ к вашему счетчику. (https://msdn.microsoft.com/en-us/library/x13ttww7.aspx)
  2. Если экземпляр блокировки когда-либо изменяется во время выполнения, у вас будет короткий момент, когда вы можете иметь состояние гонки. Один поток заблокирован на старом объекте, а другой - на новом объекте. Всегда объявляйте объекты блокировки, чтобы они не менялись. Если это статический объект, то static readonly или const - это то, что вы хотите. Если это поле внутри объекта, предназначенного для защиты одного экземпляра, объявите блокировку как readonly. Ключевое слово readonly гарантирует, что экземпляр никогда не может измениться в течение срока действия содержащейся области. (https://msdn.microsoft.com/en-us/library/acdd6hb7.aspx)
  3. Interlocked.Increment выполняет атомную замену, гарантирующую безопасность во всех работающих ядрах. Если ваш критический раздел кода просто увеличивает счетчик, этот метод избавляет систему от необходимости блокировки. Просто используйте возвращаемое значение для остальной части метода, чтобы он оставался постоянным для этой области. (https://msdn.microsoft.com/en-us/library/dd78zt0c(v=vs.110).aspx)

Для хорошего резюме субъекта, см https://stackoverflow.com/a/154803/476048

Для академических целей одни и те же проблемы и решения существующих в Java. API-интерфейсы немного отличаются друг от друга.

+2

Оператор 'lock' * уже * добавляет барьеры памяти специально, чтобы предотвратить это. Изменение переменной volatile ничего не меняет. Удаление блокировки только * уменьшает * безопасность кода, а не улучшает его. – Servy

+0

Благодарим за помощь. К сожалению, переход на неустойчивый не устранил проблему. –

+0

@savvy, прочитайте статью, которую я приложил внизу. Также у вас есть хорошее представление о том, как работают локальные кэши в многоядерных процессорах? По твоим комментариям ясно, что у вас очень неполное понимание того, какие механизмы предоставляет C#. И Interlocked.Increment полностью потокобезопасен. Попробуй. –