Оператор блокировки — обеспечение монопольного доступа к общему ресурсу
Оператор lock
получает взаимоисключающую блокировку заданного объекта перед выполнением определенных операторов, а затем снимает блокировку. Во время блокировки поток, удерживающий блокировку, может снова поставить и снять блокировку. Любой другой поток не может получить блокировку и ожидает ее снятия. Оператор lock
гарантирует, что в любой момент выполняется только один поток.
Оператор lock
имеет форму
lock (x)
{
// Your code...
}
Здесь x
— это выражение ссылочного типа. Оно является точным эквивалентом
object __lockObj = x;
bool __lockWasTaken = false;
try
{
System.Threading.Monitor.Enter(__lockObj, ref __lockWasTaken);
// Your code...
}
finally
{
if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj);
}
Так как код использует try-finally
инструкцию, блокировка освобождается даже в том случае, если исключение создается в тексте lock
инструкции.
Выражение в тексте инструкции нельзя использоватьawait
.lock
Рекомендации
При синхронизации доступа потоков к общему ресурсу блокируйте выделенный экземпляр объекта (например, private readonly object balanceLock = new object();
) или другой экземпляр, который, скорее всего, не будет использоваться как объект блокировки другими частями кода. Не используйте один и тот же экземпляр объекта блокировки для разных общих ресурсов: это может привести к взаимоблокировке или состязанию при блокировке. В частности, избегайте использования следующих экземпляров в качестве объектов блокировки:
this
, так как он может использоваться вызывающими объектами как блокировка;- Typeэкземпляры, так как они могут быть получены оператором typeof или отражением.
- экземпляры строк, включая строковые литералы, так как они могут быть интернированы.
Удерживайте блокировку в течение максимально короткого времени, чтобы сократить число конфликтов при блокировке.
Пример
В следующем примере определяется класс Account
, который синхронизирует доступ к закрытому полю balance
путем блокировки выделенного экземпляра balanceLock
. Использование одного и того же экземпляра для блокировки гарантирует, что balance
поле не может быть одновременно обновлено двумя потоками, пытающимися вызвать Debit
или Credit
методы одновременно.
using System;
using System.Threading.Tasks;
public class Account
{
private readonly object balanceLock = new object();
private decimal balance;
public Account(decimal initialBalance) => balance = initialBalance;
public decimal Debit(decimal amount)
{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "The debit amount cannot be negative.");
}
decimal appliedAmount = 0;
lock (balanceLock)
{
if (balance >= amount)
{
balance -= amount;
appliedAmount = amount;
}
}
return appliedAmount;
}
public void Credit(decimal amount)
{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "The credit amount cannot be negative.");
}
lock (balanceLock)
{
balance += amount;
}
}
public decimal GetBalance()
{
lock (balanceLock)
{
return balance;
}
}
}
class AccountTest
{
static async Task Main()
{
var account = new Account(1000);
var tasks = new Task[100];
for (int i = 0; i < tasks.Length; i++)
{
tasks[i] = Task.Run(() => Update(account));
}
await Task.WhenAll(tasks);
Console.WriteLine($"Account's balance is {account.GetBalance()}");
// Output:
// Account's balance is 2000
}
static void Update(Account account)
{
decimal[] amounts = [0, 2, -3, 6, -2, -1, 8, -5, 11, -6];
foreach (var amount in amounts)
{
if (amount >= 0)
{
account.Credit(amount);
}
else
{
account.Debit(Math.Abs(amount));
}
}
}
}
Спецификация языка C#
Дополнительные сведения см. в разделе об инструкции lock в документации Предварительная спецификация C# 6.0.
См. также
Обратная связь
https://aka.ms/ContentUserFeedback.
Ожидается в ближайшее время: в течение 2024 года мы постепенно откажемся от GitHub Issues как механизма обратной связи для контента и заменим его новой системой обратной связи. Дополнительные сведения см. в разделеОтправить и просмотреть отзыв по