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ẻ

Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
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));
            }
        }
    }
}
» Tiếp: Bảng (Table) trong WPF
« Trước: Hộp thoại (Dialog) trong C#
Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực
Copied !!!