CodeGym /Các khóa học /C# SELF /Những thực hành tốt nhất và công cụ để chẩn đoán

Những thực hành tốt nhất và công cụ để chẩn đoán

C# SELF
Mức độ , Bài học
Có sẵn

1. Bí quyết cho mã đa luồng bền vững

Nếu tưởng tượng đa luồng như một đội vài thợ cùng sửa một chiếc xe đồng thời, thì rõ ràng: chỉ cần một thợ cầm nhầm dụng cụ — là công việc tắc nghẽn. Trong code cũng vậy: xử lý không cẩn thận với dữ liệu chung dẫn tới lỗi ẩn, chỉ hiện khi “ra trận”.

Trong bài này — cách viết mã đa luồng mà không sụp như nhà lá. Và còn: những công cụ nào cứu bạn khi có vấn đề.

1. Giảm tối thiểu các critical section (lock)

Càng ít code nằm trong khối lock càng tốt. Khi một thread giữ khoá, các thread khác chờ.

Ví dụ:


// TỒI: Toàn bộ business logic trong lock — mọi thread phải chờ
lock(_locker)
{
    // Hoạt động lâu (không liên quan đến resource chung)
    Thread.Sleep(500);
    counter++;
}

// TỐT: Chỉ hành động tối thiểu trong lock
// công việc nặng ở ngoài lock
Thread.Sleep(500);
lock(_locker)
{
    counter++;
}

Thực tế: Nếu trong lock có gọi network hoặc tính toán lâu, performance sẽ giảm mạnh.

2. Không dùng đối tượng chung làm khóa

Dùng lock(this) hoặc lock(typeof(MyClass)) — là ý tưởng tệ.

Tại sao? Nếu ai đó khác dùng cùng object đó để lock, bạn sẽ gặp deadlock hoặc bug ẩn. Luôn dùng một object private riêng:

private readonly object _locker = new object();

lock(_locker)
{
    // Hành động của bạn
}

Cấm: string (string), public field, value-type objects.

3. Luôn dùng try...finally để giải phóng resource đã chiếm

Bất kỳ việc lấy Mutex, semaphore, ReaderWriterLockSlim — phải được release trong finally.

_mutex.WaitOne();
try
{
    // Critical section
}
finally
{
    _mutex.ReleaseMutex();
}

4. Đừng lạm dụng synchronization

Chỉ đồng bộ hoá truy cập tới resource chung thực sự (ví dụ collection), chứ không phải “mỗi khì”. Lock thừa biến code thành hàng đợi chờ.

5. Dùng collection và kiểu thread-safe

.NET cung cấp collections đặc biệt cho kịch bản đa luồng: ConcurrentDictionary, ConcurrentQueue, ConcurrentBag, BlockingCollection v.v. Chúng đã tự bảo vệ bên trong.

using System.Collections.Concurrent;

ConcurrentDictionary<int, string> users = new ConcurrentDictionary<int, string>();
users.TryAdd(1, "Vasya");
users[2] = "Petya";

6. Cẩn thận với deadlock (vòng khóa)

Cái bẫy điển hình — chiếm nhiều lock theo thứ tự khác nhau.

// Thread 1
lock(obj1)
{
    lock(obj2)
    {
        // Làm gì đó
    }
}

// Thread 2
lock(obj2)
{
    lock(obj1)
    {
        // Làm gì đó
    }
}

Lời khuyên: Luôn lấy lock theo cùng một thứ tự trên tất cả các thread.

7. Nếu có thể, dùng immutable state

Nếu object không thay đổi trạng thái sau khi tạo — nó an toàn để đọc từ mọi thread. Ví dụ: string, Tuple, DateTime, DTO chỉ-read.

2. Công cụ để chẩn đoán vấn đề đa luồng

Lỗi đồng bộ khó nhằn: hiếm và không đoán trước được. Dùng công cụ và phương pháp giúp bắt và phân tích chúng.

1. Logging sự kiện và thread

Log Thread.ManagedThreadId hiện tại và các thao tác mấu chốt — cách đơn giản để biết “ai và khi nào” vào/ra critical section.

Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] Đã vào critical section");
// ...
Console.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] Đã ra khỏi critical section");

Với app thực tế dùng Microsoft.Extensions.Logging, NLog, Serilog.

2. Thread Sanitizer & Race Detector

Trong .NET không có ThreadSanitizer “hoàn hảo” built-in, nhưng có công cụ hữu ích:

3. Visual Studio Diagnostics Tools

Với profiler của Visual Studio bạn có thể thấy:

  • những thread nào tồn tại trong app;
  • nơi thread đang idle (waiting);
  • nơi xảy ra lock và contention;
  • khi nào xuất hiện deadlock và contention.

Chụp traces để có đồ thị chi tiết về sử dụng lock.

4. Phân tích dump và WinDbg

Nếu server “treo”, chụp dump process và mở bằng WinDbg hoặc dotnet-dump. Qua stack trace thấy thread bị kẹt ở đâu và ai đang giữ lock nào.

Ví dụ phân tích stack:

0:000> !syncblk
Index SyncBlock MonitorHeld Recursion Owning Thread Info  SyncBlock Owner
    1 000001d4b6f90e08          1         1 000001d4b5c941c0 000001d4b6f03458

(Dump thường là công cụ của những “jadoi deploy” có kinh nghiệm — đừng sợ, đó là công cụ mạnh.)

5. Unit-test với stress (stress testing)

Chạy mã đa luồng song song hàng trăm/nghìn lần — các race hiếm sẽ dễ được tìm hơn.

[Test]
public void Counter_IsThreadSafe()
{
    var counter = 0;
    var locker = new object();
    var tasks = new List<Task>();
    for (int i = 0; i < 100; i++)
    {
        tasks.Add(Task.Run(() =>
        {
            for (int j = 0; j < 10000; j++)
            {
                lock (locker)
                {
                    counter++;
                }
            }
        }));
    }
    Task.WaitAll(tasks.ToArray());
    Assert.AreEqual(100 * 10000, counter);
}

6. Dùng asserts và checks đặc biệt

Thêm kiểm tra đảm bảo trạng thái đúng trong debug. Ví dụ, Debug.Assert khi cố gắng tái chiếm resource bởi cùng một thread.

3. Kết luận và khuyến nghị

Sơ đồ trực quan: vùng nguy hiểm và an toàn

graph TD
    A[Tài nguyên chung] -- không đồng bộ --> B(Trạng thái race)
    A -- khóa (lock/Mutex) --> C[Truy cập an toàn: critical section]
    C -- "quá nhiều lock" --> D(Mất hiệu năng)
    A -- ReaderWriterLockSlim --> E{Nhiều reader / Một writer}
    E -- "Đọc" --> F[Nhiều thread đọc cùng lúc]
    E -- "Ghi" --> G[Chỉ một ghi, các thread khác chờ]

Synchronization primitives và mục đích của chúng

Primitive Dùng để làm gì Số thread được cho phép Liên tiến trình Hiệu năng Nên dùng ở đâu
lock (Monitor)
Critical section đơn giản 1 Không Rất cao 99% các case
Mutex
Tương tự nhưng giữa các process 1 Trung bình Files, IPC
Semaphore
Tối đa N thread N Trung bình Resource pools
SemaphoreSlim
Tương tự nhưng nhanh hơn, trong process N Không Cao Pool trong code
ReaderWriterLockSlim
Nhiều reader, một writer Nhiều/1 Không Cao Cache, settings
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Loại bỏ Livelock bằng cách sử dụng tạm dừng ngẫu nhiên
Loại bỏ Livelock bằng cách sử dụng tạm dừng ngẫu nhiên
1
Khảo sát/đố vui
, cấp độ , bài học
Không có sẵn
Deadlock lẫn nhau
Những vấn đề phổ biến khi làm việc với đa luồng
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION