7

Tôi đã có nhu cầu về một cấu trúc dữ liệu đa luồng có hỗ trợ những tuyên bố:Có giải pháp hiện tại cho vấn đề cấu trúc dữ liệu đa luồng không?

  • Cho phép nhiều độc giả đồng thời và nhà văn
  • Được sắp xếp
  • là dễ dàng để lý do về

Hoàn thành nhiều người đọc và một người viết dễ dàng hơn nhiều, nhưng tôi thực sự không thể cho phép nhiều người viết.

Tôi đã nghiên cứu về lĩnh vực này và tôi biết ConcurrentSkipList (bởi Lea dựa trên công việc của Fraser và Harris) vì nó được triển khai trong Java SE 6. Tôi cũng đã triển khai phiên bản của riêng mình Danh sách Bỏ qua đồng thời dựa trên số A Provably Correct Scalable Concurrent Skip List của Herlihy, Lev, Luchangco và Shavit. Hai bản triển khai này được phát triển bởi những người sáng suốt thông minh hơn tôi, nhưng tôi vẫn (hơi xấu hổ vì nó là một công việc tuyệt vời) phải đặt ra câu hỏi nếu đây là hai triển khai thực hiện duy nhất của một trình đọc đa đồng thời./cấu trúc dữ liệu của nhà văn hiện nay?

Trả lời

3

Nghe có vẻ như bạn đang làm cho vấn đề này quá khó cho bản thân. Hãy xem xét những điều sau:

  • Dễ dàng thực hiện các phiên bản không thay đổi của nhiều cấu trúc dữ liệu, đặc biệt là cây cối. Cấu trúc dữ liệu không thay đổi có lợi ích mà, bởi vì không thay đổi được, một luồng không thể sửa đổi bộ sưu tập dưới một mũi luồng khác. Tính không thay đổi = không có điều kiện chủng tộc = không có khóa = không có khóa chết. Awesomeness.

    Xem Okasaki's Purely Functional Data Structures, cung cấp triển khai ML và Haskell của đống, cây cân bằng, ngăn xếp, hàng đợi và một số cấu trúc dữ liệu khác.

  • Chủ đề không thể thấy các thay đổi đối với cấu trúc dữ liệu không thay đổi được thực hiện trong các chủ đề khác. Tuy nhiên, họ có thể thông báo cho nhau một cách rõ ràng về các thay đổi bằng cách sử dụng đồng thời truyền thông điệp.

Khóa và mutex quá thấp và trạng thái có thể thay đổi là khá nhiều đối tượng của chương trình đa luồng. Nếu bạn nghĩ về bất kỳ vấn đề nào mà bạn đang cố gắng giải quyết về tính bất biến và thông điệp, thì nó sẽ trở nên dễ dàng hơn 1000 lần cho bạn.

+1

@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

+0

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

+0

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

2

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.

+0

@BeeOnRope: Cảm ơn một phản ứng tuyệt vời, và trong khi tôi ước mình có thể cung cấp cho bạn nhiều hơn thì tôi vẫn phải nói rằng ý tưởng sử dụng danh sách được liên kết bình thường với một mutex cho mỗi nút sẽ làm giảm hiệu suất rất nhanh, do O (n) tìm kiếm/cập nhật/chèn thời gian. Tôi thực sự cần O (log n) cho tìm kiếm/chèn/cập nhật mà về cơ bản lá bỏ qua danh sách hoặc cây nhị phân. Tôi đã cố gắng triển khai cây nhị phân nhiều đầu đọc/ghi bằng cách sử dụng khóa nhưng nó trở thành một quá trình cực kỳ phức tạp do xoay. – thr

+0

Nếu bạn có thể loại bỏ yêu cầu rằng các giá trị được sắp xếp (hoặc nếu truy cập được sắp xếp là đủ không đủ để sắp xếp đầy đủ khi cần), bản đồ băm có khóa sọc sẽ hoạt động rất tốt, có khả năng tốt hơn nhiều so với danh sách bỏ qua đồng thời. Tôi có thể hỏi tại sao danh sách bỏ qua đồng thời không đáp ứng được nhu cầu của bạn không? – BeeOnRope

0

Tôi đã tạo cấu trúc dữ liệu không có khóa trong F# với các yêu cầu tương tự cho công việc của tôi gần đây.Cụ thể, đó là một từ điển được sắp xếp ánh xạ các khóa int tới các giá trị int trong đó các giá trị là bộ đếm và hai hoạt động nguyên thủy tăng số đếm được liên kết với một khóa nhất định và thu được một mảng các cặp khóa-giá trị hiện tại.

tôi thực hiện điều này trong F# như một giá trị của các loại Map<int, int ref> ref mà là một tài liệu tham khảo có thể thay đổi vào một bản đồ bất biến từ int chìa khóa để có thể thay đổi các tham chiếu tới int giá trị. Người đọc đồng thời đọc tài liệu tham khảo để có được bản đồ hiện tại, tra cứu chìa khóa trong đó và dereference để có được giá trị int liên quan. Các nhà văn đồng thời đọc tài liệu tham khảo, tra cứu khóa và tăng nguyên tử nếu nó tồn tại hoặc tạo bản đồ mới với cặp khóa-giá trị mới và sử dụng CAS để thay thế tham chiếu gốc thành bản đồ.

Thiết kế này dựa vào khả năng đọc và ghi tài liệu tham khảo một cách nguyên tử (mà .NET đảm bảo) nhưng chỉ hiệu quả nếu các cập nhật cho Map là rất hiếm. Điều này là như vậy trong trường hợp của tôi vì hầu hết các bộ đếm gia tăng viết đã tồn tại và, trong trạng thái ổn định, việc tạo ra các quầy mới là rất hiếm.

+0

"một nhà văn duy nhất có thể cập nhật nó an toàn ..." Ông nói rằng ông muốn cho phép nhiều nhà văn cập nhật cấu trúc đồng thời. –

+0

@Seun: Bạn hoàn toàn đúng.Tôi không biết tôi đang nghĩ gì khi tôi viết câu trả lời ban đầu của mình nên tôi đã thay thế nó bằng thứ gì đó hy vọng không phải là sự vô cảm. ;-) –