Java Concurrency: CAS so với Khóa [đã đóng]


76

Tôi đang đọc Sách Java Concurrency in Practice . Trong chương 15, họ đang nói về các thuật toán không chặn và phương pháp so sánh và hoán đổi (CAS).

Người ta viết rằng CAS hoạt động tốt hơn nhiều so với các phương pháp khóa. Tôi muốn hỏi những người đã làm việc với cả hai khái niệm này và muốn biết khi nào bạn thích khái niệm nào trong số những khái niệm này? Nó có thực sự nhanh hơn rất nhiều không?

Đối với tôi, cách sử dụng ổ khóa rõ ràng và dễ hiểu hơn nhiều và thậm chí có thể tốt hơn để bảo trì (vui lòng sửa cho tôi nếu tôi sai) . Chúng ta có nên thực sự tập trung vào việc tạo mã đồng thời liên quan đến CAS hơn là khóa để tăng hiệu suất tốt hơn hay tính bền vững quan trọng hơn?

Tôi biết có thể không có một quy tắc nghiêm ngặt khi sử dụng cái gì. Nhưng tôi chỉ muốn nghe một số ý kiến, kinh nghiệm với khái niệm mới về CAS.


Khóa thông thường với các đối tượng màn hình liên quan đến CAS cũng như các cuộc gọi hạt nhân khóa. Băng cassette giảm bớt sự khóa để không có cuộc gọi hạt nhân mà không có tranh cãi. Và khi có tranh cãi, CAS có thể quay trong một thời gian và sau đó lệnh gọi hạt nhân sẽ diễn ra hoặc lệnh gọi hạt nhân sẽ diễn ra ngay lập tức.
Bonita Montero

Câu trả lời:


48

CAS nói chung nhanh hơn nhiều so với khóa, nhưng nó phụ thuộc vào mức độ tranh chấp. Bởi vì CAS có thể buộc thử lại nếu giá trị thay đổi giữa việc đọc và so sánh, một luồng về mặt lý thuyết có thể bị mắc kẹt trong trạng thái chờ đợi nếu biến được đề cập đang bị nhiều luồng khác tác động mạnh (hoặc nếu tính toán một giá trị mới rất tốn kém từ giá trị cũ (hoặc cả hai)).

Vấn đề chính với CAS là việc lập trình với chính xác khó hơn nhiều so với việc khóa. Xin lưu ý bạn, ngược lại, khóa khó sử dụng chính xác hơn nhiều so với chuyển tin nhắn hoặc STM , vì vậy đừng coi đây là một sự chứng thực cho việc sử dụng khóa.


1
Tôi đồng ý, đặt trọng tâm vào chi phí tính toán một giá trị mới . Thường thì nó rất lớn, làm cho chiến lược không khóa thua dựa trên khóa.
Alex Salauyou

28

Tốc độ tương đối của các hoạt động phần lớn không phải là một vấn đề. Điều có liên quan là sự khác biệt về khả năng mở rộng giữa các thuật toán dựa trên khóa và không chặn. Và nếu bạn đang chạy trên hệ thống lõi 1 hoặc 2, hãy ngừng suy nghĩ về những điều như vậy.

Thuật toán không chặn thường mở rộng quy mô tốt hơn vì chúng có "phần quan trọng" ngắn hơn so với thuật toán dựa trên khóa.


23

Bạn có thể nhìn vào các số giữa a ConcurrentLinkedQueuevà a BlockingQueue. Những gì bạn sẽ thấy là CAS nhanh hơn đáng kể trong điều kiện tranh chấp luồng vừa phải (thực tế hơn trong các ứng dụng thế giới thực).

Đặc tính hấp dẫn nhất của thuật toán không chặn là thực tế là nếu một luồng bị lỗi (lỗi bộ nhớ cache, hoặc tệ hơn là lỗi seg) thì các luồng khác sẽ không nhận thấy lỗi này và có thể tiếp tục. Tuy nhiên, khi có được một khóa, nếu chuỗi giữ khóa bị lỗi hệ điều hành nào đó, thì mọi chuỗi khác đang chờ khóa được giải phóng cũng sẽ bị lỗi.

Để trả lời câu hỏi của bạn, vâng, các thuật toán hoặc bộ sưu tập an toàn chuỗi không chặn ( ConcurrentLinkedQueue, ConcurrentSkipListMap/Set) có thể nhanh hơn đáng kể so với các thuật toán chặn của chúng. Như Marcelo đã chỉ ra, việc làm đúng các thuật toán không chặn là rất khó và cần phải cân nhắc rất nhiều.

Bạn nên đọc về Hàng đợi Michael và Scott , đây là cách triển khai hàng đợi ConcurrentLinkedQueuevà giải thích cách xử lý một hàm nguyên tử, an toàn theo luồng, hai chiều với một CAS duy nhất .


1
Bài báo về "Hàng đợi Michael và Scott" thật thú vị. Cám ơn!
Prine

14

Có một cuốn sách hay liên quan nhiều đến chủ đề đồng thời không có khóa: "Nghệ thuật lập trình đa xử lý" của Maurice Herlihy


Ngoài ra phần trình bày của anh ấy phần 1phần 2 rất đáng giá.
Tamas Ionut

9

Nếu bạn đang tìm kiếm một so sánh thế giới thực, thì đây là một. Ứng dụng của chúng tôi có hai (2) luồng 1) Một luồng người đọc để bắt gói mạng và 2) một luồng người tiêu dùng lấy gói, đếm nó và báo cáo thống kê.

Luồng số 1 trao đổi một gói duy nhất tại một thời điểm với luồng số 2

Kết quả # 1 - sử dụng trao đổi dựa trên CAS tùy chỉnh bằng cách sử dụng các nguyên tắc tương tự như SynchronousQueue , trong đó lớp của chúng ta được gọi là CASSynchronousQueue :

30,766,538 packets in 59.999 seconds ::  500.763Kpps, 1.115Gbps 0 drops
libpcap statistics: recv=61,251,128, drop=0(0.0%), ifdrop=0

Kết quả # 2 - khi chúng tôi thay thế việc triển khai CAS bằng SynchronousQueue của java tiêu chuẩn :

8,782,647 packets in 59.999 seconds ::  142.950Kpps, 324.957Mbps 0 drops 
libpcap statistics: recv=69,955,666, drop=52,369,516(74.9%), ifdrop=0

Tôi không nghĩ rằng sự khác biệt về hiệu suất không thể rõ ràng hơn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.