Vâng, bạn đã xác định một trong những tôi thường sẽ đề nghị - các skiplist đồng thời, nhưng sự vắng mặt của các yêu cầu cụ thể nào khác ngoài ba ở trên, tôi nghĩ rằng một danh sách liên kết đơn giản với mutexes mỗi nút sẽ làm việc:
Mỗi nút chứa một phần tử (hoặc một tham chiếu) và một mutex đơn giản. Tôi sẽ giả sử Java ở đây, bởi vì nó giúp tránh điều kiện chủng tộc xung quanh nút khai hoang. Tìm kiếm thông qua danh sách bao gồm lặp qua các nút từ đầu, không cần lấy bất kỳ khóa nào (mặc dù bạn sẽ cần phải đảm bảo khả năng hiển thị giữa các chủ đề tại một số thời điểm - bạn có thể chọn mức độ thường xuyên xảy ra - một lần cho mỗi tìm kiếm có thể đủ tốt).
Để thêm mục, thực hiện tìm kiếm cho đến khi bạn tìm thấy nút tiền nhiệm và nút kế thừa trước đó cho giá trị bạn muốn chèn, khóa mutex liên kết với nút trước đó, kiểm tra nút kế tiếp lần nữa (có thể đã thay đổi từ dưới bạn), sau đó ghép vào nút mới.
Thao tác xóa hoạt động tương tự - tìm nút tiền thân của nút bạn muốn xóa, khóa nút tiền thân, kiểm tra xem nút đó vẫn là nút tiền thân, và lấy nó ra khỏi cây.
Cấu trúc này được sắp xếp (đến mức nó đúng - tôi chưa chứng minh điều đó!).
Cấu trúc này rõ ràng cho phép nhiều người đọc (người đọc không bao giờ bị chặn vì lý do nào) và nhiều nhà văn, mặc dù các nhà văn cố gắng thao tác cùng một phần danh sách (ví dụ: hai chuỗi chèn nút có cùng điểm nối) chờ đợi nhau.
Cấu trúc có vẻ tương đối dễ hiểu - dựa trên một danh sách liên kết duy nhất, với cấu trúc khóa khá đơn giản và với một vài bất biến đơn giản. Tôi đã không dành nhiều hơn một vài phút lý luận về tính chính xác của nó, tuy nhiên. Bạn có thể làm cho nó đơn giản hơn để giải thích, với chi phí hiệu suất, bằng cách làm cho chính sách khóa nặng hơn - để chèn và xóa khóa mỗi nút trong quá trình lặp và sau đó khóa người kế thừa trước khi mở khóa nút trước - theo cách này bạn sẽ có cả hai nút bị khóa khi bạn tìm thấy điểm nối hoặc điểm xóa, do đó không cần "kiểm tra lại, quay lại".
Bạn có thể loại bỏ hoàn toàn khóa và sử dụng danh sách không khóa trong khi vẫn duy trì trạng thái "phải được sắp xếp", nhưng tôi chưa nghĩ về điều này - tối thiểu tôi nghi ngờ nó sẽ "khó hơn".
Trong C++ cấu trúc có liên quan hơn, vì bạn không thể dựa vào GC để giữ cho các nút xung quanh miễn là người đọc có thể nhìn vào chúng, vì vậy chiến lược đơn giản cho phép người đọc mosey xung quanh trong một khóa - cách miễn phí không bay, nếu bạn muốn xóa các nút. Bạn có thể điều chỉnh nó bằng cách làm cho người đọc lấy khóa của mỗi nút truy cập, nhưng điều này cho cả hiệu suất (rõ ràng) và đồng thời (bởi vì mặc dù bạn có thể có nhiều độc giả và nhà văn theo cách cơ bản, họ không bao giờ có thể vượt qua nhau nữa, vì vậy đồng thời thực tế là rất hạn chế).
Một giải pháp thay thế sẽ là sử dụng rwlocks trong mỗi nút, người đọc chỉ đọc khóa và trình chèn/deleters lấy khóa đọc để tìm vị trí hoạt động, sau đó nâng cấp để viết. Tiền tệ ít nhất được cải thiện vì người đọc có thể vượt qua người đọc, nhưng người viết vẫn chặn người đọc (vì vậy tất cả người đọc đang lặp lại ở vị trí trước khi một nút nào đó bắt đầu hoạt động ghi sẽ không thể lặp qua nút đó cho đến khi thao tác hoàn tất).
Bây giờ, tất cả những gì đã nói (whew!) Tôi nên đề cập rằng khác "dễ dàng để lý do" cấu trúc này có vẻ khá kém hơn so với danh sách bỏ qua đồng thời trong mọi cách vật chất, với ngoại lệ có thể sử dụng bộ nhớ ít hơn một chút (có lẽ). Nó không có hành vi tìm kiếm nhật ký (N), đặc biệt. Nó không phải là hoàn toàn không có khóa (các nhà văn có thể đợi các nhà văn trong một số trường hợp). Tôi thậm chí không chắc chắn rằng đó là dễ dàng hơn nhiều để lý do về như xa như concurrency đi, mặc dù cấu trúc cơ bản là đơn giản.
Tôi nghĩ nếu bạn thực sự muốn bạn có thể mở rộng loại khóa "trên mỗi nút" này thành một cái gì đó giống như cây rb nếu bạn muốn, nhưng nó không dễ dàng. Đặc biệt, bạn cần phải tìm một số loại nút để khóa trước bất kỳ phép xoay cây nào là "đủ cao" để đảm bảo rằng bất kỳ chuỗi nào muốn thực hiện sửa đổi sẽ ảnh hưởng đến độ chính xác của vòng xoay của bạn cũng sẽ cố gắng khóa cùng một nút. Trong danh sách liên kết, đây là nút "tiền thân" - trong một cây AVL hoặc cây RB, nó không đơn giản như vậy.
@Juliet: Vấn đề với việc sử dụng cấu trúc dữ liệu không thay đổi là đối với ứng dụng của tôi, nó cơ bản giống như sử dụng nhiều người đọc và một người viết (hiệu suất khôn ngoan). Vì nếu hai nhà văn (với một cấu trúc không thay đổi) cả hai chèn một nút, thì một trong số họ phải khởi động lại, với cây mới được tạo ra bởi nhà văn cạnh tranh đã thay đổi nó thông qua đầu tiên. – thr
Có rất nhiều chiến lược để giải quyết vấn đề này. Trong vũ trụ MapReduce, chúng ta có một vấn đề lớn có thể chia thành các vấn đề phụ. Ví dụ, giả sử chúng ta muốn tìm 1.000.000 số nguyên tố đầu tiên sử dụng phân chia thử nghiệm. Chúng ta có thể khởi động 100 chủ đề con, trong đó chủ đề đầu tiên kiểm tra số 1-999, bài kiểm tra luồng thứ hai 1000-1999, các bài kiểm tra tiếp theo 2000-3999, v.v. Khi mỗi luồng hoàn thành, nó chuyển kết quả của nó đến một luồng khác, kết hợp tất cả các kết quả riêng lẻ vào một kết quả duy nhất. – Juliet
Nếu bạn không thể phân chia vấn đề của mình thành các bài toán con, phần sau cũng hoạt động: một luồng chính giữ cấu trúc dữ liệu của bạn. Chuỗi chính hiển thị hai thuộc tính, Chèn và Truy vấn, trong đó các phương thức này sửa đổi hoặc trả về cấu trúc dữ liệu trong trạng thái hiện tại tương ứng. Các chuỗi con có thể gọi các phương thức trên luồng chính để cập nhật hoặc truy vấn cấu trúc dữ liệu. Ngược lại với chiến lược này là bản cập nhật nguyên tử (và, nếu được triển khai đúng, giao dịch) và chủ đề không thể ghi đè lên nhau. Nhược điểm là các chủ đề con không có quyền truy cập thời gian thực vào cấu trúc dữ liệu. – Juliet