Tôi đã đọc về các kỹ thuật không có khóa, như So sánh và trao đổi và tận dụng các lớp Interlocked và SpinWait để đạt được đồng bộ hóa luồng mà không cần khóa.Khóa vs So sánh và hoán đổi
Tôi đã chạy một vài thử nghiệm của riêng mình, nơi tôi chỉ đơn giản là có nhiều chủ đề cố gắng gắn thêm một ký tự vào một chuỗi. Tôi đã thử sử dụng thường xuyên lock
s và so sánh và trao đổi. Đáng ngạc nhiên (ít nhất là với tôi), ổ khóa cho thấy kết quả tốt hơn nhiều so với sử dụng CAS.
Đây là phiên bản CAS của mã của tôi (dựa trên this). Nó sau một mô hình copy-> Modify-> swap:
private string _str = "";
public void Append(char value)
{
var spin = new SpinWait();
while (true)
{
var original = Interlocked.CompareExchange(ref _str, null, null);
var newString = original + value;
if (Interlocked.CompareExchange(ref _str, newString, original) == original)
break;
spin.SpinOnce();
}
}
Và phiên bản khóa đơn giản (và hiệu quả hơn):
private object lk = new object();
public void AppendLock(char value)
{
lock (lk)
{
_str += value;
}
}
Nếu tôi cố gắng thêm 50.000 ký tự, phiên bản CAS mất 1.2 giây và khóa phiên bản 700ms (trung bình). Đối với 100k ký tự, chúng mất 7 giây và 3,8 giây tương ứng. Điều này được chạy trên một quad-core (i5 2500k).
Tôi nghi ngờ lý do CAS hiển thị các kết quả này là do không thực hiện bước "hoán đổi" cuối cùng nhiều. Tôi đã đúng. Khi tôi thử thêm 50 nghìn ký tự (50 nghìn lần hoán đổi thành công), tôi có thể đếm từ 70k (trường hợp tốt nhất) và gần như 200k (kịch bản xấu nhất) thất bại. Kịch bản trường hợp xấu nhất, 4 trong số 5 lần thử không thành công.
Vì vậy, câu hỏi của tôi là:
- tôi thiếu gì? CAS có nên cho kết quả tốt hơn không? Lợi ích ở đâu?
- Tại sao CAS chính xác và khi nào là một lựa chọn tốt hơn? (Tôi biết điều này đã được yêu cầu, nhưng tôi không thể tìm thấy bất kỳ câu trả lời thỏa mãn nào cũng giải thích kịch bản cụ thể của tôi).
Đó là sự hiểu biết của tôi rằng các giải pháp sử dụng CAS, mặc dù khó viết mã, quy mô tốt hơn nhiều và thực hiện tốt hơn so với khóa khi tranh chấp tăng lên. Trong ví dụ của tôi, các hoạt động rất nhỏ và thường xuyên, có nghĩa là tranh chấp cao và tần số cao. Vậy tại sao các bài kiểm tra của tôi cho thấy khác?
Tôi giả định rằng các hoạt động dài hơn sẽ làm cho trường hợp thậm chí tệ hơn -> tỷ lệ thất bại "hoán đổi" sẽ tăng nhiều hơn.
PS: đây là đoạn code tôi sử dụng để chạy các bài kiểm tra:
Stopwatch watch = Stopwatch.StartNew();
var cl = new Class1();
Parallel.For(0, 50000, i => cl.Append('a'));
var time = watch.Elapsed;
Debug.WriteLine(time.TotalMilliseconds);
Không, bạn không đo thời gian thực hiện của CAS, nhưng chủ yếu là thời gian thực hiện so sánh chuỗi. Lớp Interlocked không may không có hoạt động đọc-sửa-ghi nguyên tử cho các kiểu tham chiếu (đó là những gì bạn đang làm trong ví dụ "khóa" của bạn mà không dựa vào so sánh chuỗi.) – elgonzo
Giải pháp khóa miễn phí của bạn đang làm nhiều việc hơn khóa phiên bản. Đầu tiên, 'CompareExchange' ban đầu để đọc giá trị hiện tại là quá mức cần thiết, thực hiện một phép đọc dễ bay hơi (' Thread.VolatileRead') sẽ cho bạn kết quả tương tự mà không có chi phí thấp hơn. Thứ hai, mỗi bản cập nhật đã cố gắng trong vòng lặp sẽ lặp lại giá trị "hiện tại" của chuỗi và nối thêm các giá trị mới. Bạn không thể làm bất cứ điều gì về điều này, nhưng phiên bản khóa không bị vấn đề này. Đây là bản sao chuỗi có nhiều khả năng gây ra phần lớn chênh lệch thời gian. – William
Đối với chúng tôi những con người chỉ đơn thuần, gắn bó với việc sử dụng các ổ khóa hiện tại thay vì cố gắng cuộn của riêng bạn. Đa luồng là đủ cứng mà không phải đối phó với các vấn đề [ABA] (http://en.wikipedia.org/wiki/ABA_problem). – William