Возможно, вы столкнулись с некоторыми эффектами кеша для локальных кешей на несвязанных ядрах. Проблема заключается в том, что сам 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()
.
Понимание, почему это работает
- Среда CLR выполняет оптимизацию и перекомпиляции кода во время выполнения. Ключевое слово
volatile
гарантирует, что оптимизация не включает доступ к вашему счетчику. (https://msdn.microsoft.com/en-us/library/x13ttww7.aspx)
- Если экземпляр блокировки когда-либо изменяется во время выполнения, у вас будет короткий момент, когда вы можете иметь состояние гонки. Один поток заблокирован на старом объекте, а другой - на новом объекте. Всегда объявляйте объекты блокировки, чтобы они не менялись. Если это статический объект, то
static readonly
или const
- это то, что вы хотите. Если это поле внутри объекта, предназначенного для защиты одного экземпляра, объявите блокировку как readonly
. Ключевое слово readonly
гарантирует, что экземпляр никогда не может измениться в течение срока действия содержащейся области. (https://msdn.microsoft.com/en-us/library/acdd6hb7.aspx)
Interlocked.Increment
выполняет атомную замену, гарантирующую безопасность во всех работающих ядрах. Если ваш критический раздел кода просто увеличивает счетчик, этот метод избавляет систему от необходимости блокировки. Просто используйте возвращаемое значение для остальной части метода, чтобы он оставался постоянным для этой области. (https://msdn.microsoft.com/en-us/library/dd78zt0c(v=vs.110).aspx)
Для хорошего резюме субъекта, см https://stackoverflow.com/a/154803/476048
Для академических целей одни и те же проблемы и решения существующих в Java. API-интерфейсы немного отличаются друг от друга.
Покажите нам больше кода. Это в winforms? Как и где вы называете «OutputNum»? – dotctor
Я бы сказал, что вы, вероятно, переустанавливаете «TheLock» где-то. В любом случае код, который вы нам показали, не содержит проблемы. –
Я добавлю еще код, в течение одной секунды. Это консольное приложение. –