lock
является мьютексом и работает как POSIX pthread_mutex_lock
и pthread_mutex_unlock
в C.
Только один фрагмент кода может получить блокировку для данного объекта одновременно, поэтомуспособ синхронизации потоков (не обязательно лучший способ, но это гораздо более подробный и контекстуальный ответ).
Например, следующий код запускает пару потоков одновременно:
- один увеличивает каждый элемент массива чисел на 10,
- другой печатает содержимое массива
var numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Task.WhenAll(
Task.Run(() =>
{
for (var i = 0; i < 10; i++)
{
numbers[i] += 10;
Thread.Sleep(10);
}
}),
Task.Run(() =>
{
foreach (var i in numbers)
{
Console.Write(i + " ");
Thread.Sleep(10);
}
})
);
Поскольку они запускаются одновременно, вывод выглядит примерно так:
11 2 13 4 56 7 18 9 10
Некоторые числа увеличиваются, другие нет, и каждый раз они разные.
Тот же код с циклами, заключенными в блокировку, однако:
object threadLock = new object();
var numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Task.WhenAll(
Task.Run(() =>
{
lock (threadLock)
{
for (var i = 0; i < 10; i++)
{
numbers[i] += 10;
Thread.Sleep(10);
}
}
}),
Task.Run(() =>
{
lock (threadLock)
{
foreach (var i in numbers)
{
Console.Write(i + " ");
Thread.Sleep(10);
}
}
})
);
Это только когда-либо выводит одну из двух вещей, в зависимости от того, какой цикл получает блокировку первым:
11 12 13 14 15 16 17 18 19 20
или
1 2 3 4 5 6 7 8 9 10
Фактической координации между этими двумя задачами нет, поэтому набор, который вы получаете (увеличивается или нет), зависит только от того, какой из них получит блокировку первым.