C# - C Sharp: Câu lệnh lock - Đảm bảo quyền truy cập duy nhất vào tài nguyên được chia sẻ
Giải phóng thời gian, khai phóng năng lực
Câu lệnh lock
lấy khóa loại trừ lẫn nhau cho một đối tượng nhất định, thực thi một khối câu lệnh, sau đó giải phóng khóa. Trong khi khóa được giữ, thì thread đang giữ khóa có thể lấy lại và giải phóng khóa. Các thread khác đều bị chặn lấy khóa và phải đợi cho đến khi khóa được giải phóng. Câu lệnh lock
đảm bảo rằng một luồng duy nhất có quyền truy cập độc quyền vào đối tượng đó.
Câu lệnh lock
có dạng:
lock (x)
{
// Your code...
}
Trong đó x
là một biểu thức có kiểu tham chiếu. Câu lệnh lock ở trên chính xác tương đương với:
object __lockObj = x; bool __lockWasTaken = false; try { System.Threading.Monitor.Enter(__lockObj, ref __lockWasTaken); // Your code... } finally { if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj); }
Vì có sử dụng khối try...finally, nên khóa được giải phóng ngay cả khi một ngoại lệ được ném vào phần nội dung của câu lệnh lock
.
Bạn không thể sử dụng toán tử await trong phần nội dung của câu lệnh lock
.
Hướng dẫn
Khi bạn đồng bộ hóa quyền truy cập luồng vào tài nguyên được chia sẻ, hãy khóa trên một phiên bản đối tượng chuyên dụng (ví dụ: private readonly object balanceLock = new object();
) hoặc một phiên bản khác không có khả năng được sử dụng làm đối tượng khóa bởi các phần không liên quan của mã. Tránh sử dụng cùng một phiên bản đối tượng khóa cho các tài nguyên được chia sẻ khác nhau, vì nó có thể dẫn đến bế tắc hoặc tranh chấp khóa. Đặc biệt, tránh sử dụng các loại sau đây làm đối tượng khóa:
this
, vì nó có thể được người gọi sử dụng làm khóa.- Các thể hiện Type, vì những đối tượng đó có thể được lấy bởi toán tử typeof hoặc phản chiếu.
- Các trường hợp chuỗi, bao gồm cả chuỗi ký tự, vì chuỗi ký tự có thể được thực hiện.
Giữ khóa trong thời gian ngắn nhất có thể để giảm tranh chấp khóa.
Ví dụ
Ví dụ sau định nghĩa một lớp Account
đồng bộ hóa quyền truy cập vào trường private balance
của nó bằng cách khóa trên một thể hiện balanceLock
chuyên dụng. Sử dụng cùng một phiên bản để khóa đảm bảo rằng trường balance
không thể được cập nhật đồng thời bởi hai thread đang cố gọi phương thức Debit
hoặc Credit
đồng thời.
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)); } } } }
Giải phóng thời gian, khai phóng năng lực