Làm thế nào tôi có thể làm cho một công trình phổ quát hiệu quả hơn?


16

"Cấu trúc phổ quát" là một lớp bao bọc cho một đối tượng tuần tự cho phép nó được tuyến tính hóa (một điều kiện nhất quán mạnh cho các đối tượng đồng thời). Chẳng hạn, đây là một cấu trúc chờ miễn phí được điều chỉnh, trong Java, từ [1], giả sử sự tồn tại của hàng đợi chờ thỏa mãn giao diện WFQ(chỉ yêu cầu sự đồng thuận một lần giữa các luồng) và giả định Sequentialgiao diện:

public interface WFQ<T> // "FIFO" iteration
{
    int enqueue(T t); // returns the sequence number of t
    Iterable<T> iterateUntil(int max); // iterates until sequence max
}
public interface Sequential
{
    // Apply an invocation (method + arguments)
    // and get a response (return value + state)
    Response apply(Invocation i); 
}
public interface Factory<T> { T generate(); } // generate new default object
public interface Universal extends Sequential {}

public class SlowUniversal implements Universal
{
    Factory<? extends Sequential> generator;
    WFQ<Invocation> wfq = new WFQ<Invocation>();
    Universal(Factory<? extends Sequential> g) { generator = g; } 
    public Response apply(Invocation i)
    {
        int max = wfq.enqueue(i);
        Sequential s = generator.generate();
        for(Invocation invoc : wfq.iterateUntil(max))
            s.apply(invoc);
        return s.apply(i);
    }
}

Việc triển khai này không thỏa mãn lắm vì nó rất chậm (bạn nhớ mọi lời gọi và phải phát lại mỗi lần áp dụng - chúng tôi có thời gian chạy tuyến tính theo kích thước lịch sử). Có cách nào để chúng tôi có thể mở rộng WFQSequentialgiao diện (theo cách hợp lý) để cho phép chúng tôi lưu một số bước khi áp dụng lệnh gọi mới không?

Chúng ta có thể làm điều này hiệu quả hơn (không phải thời gian chạy tuyến tính trong kích thước lịch sử, tốt nhất là sử dụng bộ nhớ cũng giảm) mà không làm mất thuộc tính chờ đợi?

Làm rõ

"Cấu trúc phổ quát" là một thuật ngữ mà tôi khá chắc chắn được tạo ra bởi [1], nó chấp nhận một đối tượng không an toàn nhưng tương thích với luồng, được Sequentialgiao diện chung. Sử dụng hàng đợi không chờ đợi, cấu trúc đầu tiên cung cấp một phiên bản tuyến tính , an toàn cho luồng của đối tượng cũng không phải chờ đợi (điều này giả định tính xác định và các applyhoạt động tạm dừng ).

Điều này là không hiệu quả, vì phương pháp này có hiệu quả khi mỗi luồng cục bộ bắt đầu từ một bảng rõ ràng và áp dụng mọi hoạt động từng được ghi lại cho nó. Trong mọi trường hợp, điều này hoạt động vì nó đạt được hiệu quả đồng bộ hóa bằng cách sử dụng WFQđể xác định thứ tự áp dụng tất cả các hoạt động: mọi cuộc gọi luồng applysẽ thấy cùng một Sequentialđối tượng cục bộ , với cùng một chuỗi Invocations được áp dụng cho nó.

Câu hỏi của tôi là liệu chúng ta có thể (ví dụ) giới thiệu quy trình dọn dẹp nền tảng cập nhật "trạng thái bắt đầu" để chúng ta không phải khởi động lại từ đầu. Điều này không đơn giản như có một con trỏ nguyên tử với một con trỏ bắt đầu - những cách tiếp cận này dễ dàng mất đi sự đảm bảo không phải chờ đợi. Sự nghi ngờ của tôi là một số cách tiếp cận dựa trên hàng đợi khác có thể hoạt động ở đây.

Biệt ngữ:

  1. chờ đợi - bất kể số lượng luồng hoặc ra quyết định của người lập lịch, applysẽ chấm dứt trong một số lượng các hướng dẫn có thể chứng minh được thực thi cho luồng đó.
  2. không khóa - giống như trên, nhưng thừa nhận khả năng thời gian thực hiện không giới hạn, chỉ trong trường hợp số lượng applyhoạt động không giới hạn đang được thực hiện trong các luồng khác. Thông thường, các chương trình đồng bộ hóa lạc quan thuộc loại này.
  3. chặn - hiệu quả tại sự thương xót của người lập lịch.

Một ví dụ hoạt động, theo yêu cầu (hiện tại trên một trang sẽ không hết hạn)

[1] Herlihy và Shavit, Nghệ thuật lập trình đa xử lý .


Câu hỏi 1 chỉ có thể trả lời nếu chúng tôi biết "công việc" có nghĩa gì với bạn.
Robert Harvey

@RobertHarvey Tôi đã sửa nó - tất cả những gì nó cần để "làm việc" là để trình bao bọc không phải chờ đợi và tất cả các hoạt động trên CopyableSequentiallà hợp lệ - tính ổn định tuyến tính nên tuân theo thực tế là nó Sequential.
VF1

Có rất nhiều từ có ý nghĩa trong câu hỏi này nhưng tôi đang đấu tranh để đặt chúng lại với nhau để hiểu chính xác những gì bạn đang cố gắng thực hiện. Bạn có thể cung cấp một số lời giải thích về vấn đề mà bạn đang cố gắng giải quyết và có thể làm giảm bớt thuật ngữ một chút không?
JimmyJames

@JimmyJames Tôi đã xây dựng một "bình luận mở rộng" bên trong câu hỏi. Xin vui lòng cho tôi biết nếu có bất kỳ Jargon khác để làm sáng tỏ.
VF1

trong đoạn đầu tiên của bình luận, bạn nói "đối tượng không an toàn nhưng tương thích với luồng" và "phiên bản tuyến tính hóa của đối tượng". Không rõ ý của bạn là gì bởi vì an toàn luồngtuyến tính hóa chỉ thực sự phù hợp với các hướng dẫn thực thi nhưng bạn đang sử dụng chúng để mô tả các đối tượng là dữ liệu. Tôi đoán rằng Invocation (không được xác định) thực sự là một con trỏ phương thức và đó là phương thức không an toàn cho luồng. Tôi không biết những gì có nghĩa là tương thích chủ đề .
JimmyJames

Câu trả lời:


1

Đây là một lời giải thích và ví dụ về cách thực hiện điều này. Hãy cho tôi biết nếu có những phần không rõ ràng.

Gist với nguồn

phổ cập

Khởi tạo:

Chỉ mục chủ đề được áp dụng theo kiểu tăng nguyên tử. Điều này được quản lý bằng cách sử dụng một AtomicIntegertên nextIndex. Các chỉ mục này được gán cho các luồng thông qua một ThreadLocalthể hiện tự khởi chạy bằng cách lấy chỉ mục tiếp theo từ đó nextIndexvà tăng nó. Điều này xảy ra lần đầu tiên khi chỉ mục của mỗi luồng được lấy lần đầu tiên. A ThreadLocalđược tạo để theo dõi chuỗi cuối cùng mà chủ đề này tạo ra. Nó được khởi tạo 0. Tham chiếu đối tượng nhà máy tuần tự được truyền vào và lưu trữ. Hai AtomicReferenceArraytrường hợp được tạo ra kích thước n. Đối tượng đuôi được gán cho từng tham chiếu, đã được khởi tạo với trạng thái ban đầu do Sequentialnhà máy cung cấp . nlà số lượng chủ đề tối đa được phép. Mỗi phần tử trong các mảng 'thuộc về chỉ mục luồng tương ứng.

Áp dụng phương pháp:

Đây là phương pháp làm công việc thú vị. Nó làm như sau:

  • Tạo một nút mới cho lệnh gọi này: của tôi
  • Đặt nút mới này trong mảng thông báo tại chỉ mục của luồng hiện tại

Sau đó, vòng lặp trình tự bắt đầu. Nó sẽ tiếp tục cho đến khi lệnh gọi hiện tại đã được giải trình tự:

  1. tìm một nút trong mảng thông báo bằng cách sử dụng chuỗi của nút cuối cùng được tạo bởi luồng này. Thêm về điều này sau.
  2. nếu một nút được tìm thấy ở bước 2 thì nó chưa được tuần tự, hãy tiếp tục với nó, nếu không, chỉ cần tập trung vào lời gọi hiện tại. Điều này sẽ chỉ cố gắng giúp một nút khác cho mỗi lần gọi.
  3. Bất cứ nút nào được chọn trong bước 3, hãy tiếp tục thử trình tự sau nút thứ tự cuối cùng (các luồng khác có thể can thiệp.) Bất kể thành công, hãy đặt tham chiếu đầu của các luồng hiện tại vào chuỗi được trả về bởi decideNext()

Chìa khóa của vòng lặp lồng nhau được mô tả ở trên là decideNext()phương thức. Để hiểu điều đó, chúng ta cần nhìn vào lớp Node.

Lớp nút

Lớp này chỉ định các nút trong danh sách liên kết đôi. Không có nhiều hành động trong lớp này. Hầu hết các phương thức là các phương thức truy xuất đơn giản nên khá tự giải thích.

phương pháp đuôi

cái này trả về một thể hiện nút đặc biệt với một chuỗi 0. Nó chỉ hoạt động như một trình giữ chỗ cho đến khi một lệnh gọi thay thế nó.

Thuộc tính và khởi tạo

  • seq: số thứ tự, được khởi tạo thành -1 (có nghĩa là không có kết quả)
  • invocation: giá trị của lời gọi của apply(). Đặt khi xây dựng.
  • next: AtomicReferencecho liên kết chuyển tiếp. một khi được giao, điều này sẽ không bao giờ được thay đổi
  • previous: AtomicReferencecho liên kết ngược được chỉ định khi giải trình tự và bị xóa bởitruncate()

Quyết định tiếp theo

Phương thức này chỉ là một trong Node với logic không tầm thường. Tóm lại, một nút được cung cấp như một ứng cử viên để trở thành nút tiếp theo trong danh sách được liên kết. Các compareAndSet()phương pháp sẽ kiểm tra nếu nó tham khảo là null và nếu như vậy, thiết lập các tham chiếu đến các ứng cử viên. Nếu tham chiếu đã được đặt, nó không làm gì cả. Hoạt động này là nguyên tử vì vậy nếu hai ứng cử viên được cung cấp cùng một lúc, chỉ có một ứng cử viên sẽ được chọn. Điều này đảm bảo chỉ có một nút sẽ được chọn là nút tiếp theo. Nếu nút ứng cử viên được chọn, chuỗi của nó được đặt thành giá trị tiếp theo và liên kết trước đó được đặt thành nút này.

Nhảy lại phương thức áp dụng lớp Universal ...

Đã gọi decideNext()nút được giải trình tự cuối cùng (khi được chọn) bằng nút của chúng tôi hoặc nút từ announcemảng, có hai lần xuất hiện có thể xảy ra: 1. Nút đã được giải trình tự thành công 2. Một số luồng khác đã xử lý trước luồng này.

Bước tiếp theo là kiểm tra xem nút được tạo cho lệnh gọi này. Điều này có thể xảy ra bởi vì chủ đề này đã giải trình tự thành công nó hoặc một số chủ đề khác nhặt nó từ announcemảng và giải trình tự cho chúng tôi. Nếu nó chưa được giải trình tự, quá trình được lặp lại. Mặt khác, cuộc gọi kết thúc bằng cách xóa mảng thông báo cho chỉ mục của luồng này và trả về giá trị kết quả của lệnh gọi. Mảng thông báo được xóa để đảm bảo không có tham chiếu nào đến nút còn lại xung quanh sẽ ngăn nút được thu gom rác và do đó giữ tất cả các nút trong danh sách được liên kết từ thời điểm đó còn tồn tại.

Đánh giá phương pháp

Bây giờ nút của lệnh gọi đã được giải trình tự thành công, yêu cầu cần phải được đánh giá. Để làm điều đó, bước đầu tiên là đảm bảo rằng các yêu cầu trước cái này đã được đánh giá. Nếu họ không có chủ đề này sẽ không chờ đợi nhưng sẽ thực hiện công việc đó ngay lập tức.

Phương pháp SureP Warrior

Các ensurePrior()phương pháp làm việc này bằng cách kiểm tra các nút trước đó trong danh sách liên kết. Nếu trạng thái của nó không được đặt, nút trước đó sẽ được đánh giá. Nút đó là đệ quy. Nếu nút trước nút trước chưa được đánh giá, nó sẽ gọi đánh giá cho nút đó và cứ thế tiếp tục.

Bây giờ nút trước đó được biết là có trạng thái, chúng ta có thể đánh giá nút này. Nút cuối cùng được lấy và gán cho một biến cục bộ. Nếu tham chiếu này là null, điều đó có nghĩa là một số luồng khác đã lấy trước cái này và đã đánh giá nút này; thiết lập trạng thái của nó. Mặt khác, trạng thái của nút trước được truyền cho Sequentialphương thức áp dụng của đối tượng cùng với lời gọi của nút này. Trạng thái được trả về được đặt trên nút và truncate()phương thức được gọi, xóa liên kết ngược khỏi nút vì nó không còn cần thiết nữa.

Phương pháp MoveForward

Phương thức di chuyển về phía trước sẽ cố gắng di chuyển tất cả các tham chiếu đầu đến nút này nếu chúng chưa được trỏ đến một cái gì đó xa hơn. Điều này là để đảm bảo rằng nếu một luồng ngừng gọi, thì đầu của nó sẽ không giữ lại một tham chiếu đến một nút không còn cần thiết nữa. Các compareAndSet()phương pháp sẽ đảm bảo chúng tôi chỉ cập nhật nút nếu một số chủ đề khác vẫn không thay đổi nó vì nó đã được lấy ra.

Thông báo mảng và giúp đỡ

Chìa khóa để làm cho cách tiếp cận này không phải chờ đợi thay vì chỉ đơn giản là không khóa là chúng ta không thể cho rằng bộ lập lịch xử lý sẽ ưu tiên cho mỗi luồng khi cần. Nếu mỗi luồng chỉ đơn giản là cố gắng sắp xếp các nút riêng của nó, thì có thể một luồng có thể liên tục được tải trước khi tải. Để giải thích cho khả năng này, trước tiên, mỗi luồng sẽ cố gắng 'trợ giúp' các luồng khác có thể không được giải trình tự.

Ý tưởng cơ bản là khi mỗi luồng tạo thành công các nút, các chuỗi được gán sẽ tăng đơn điệu. Nếu một luồng hoặc luồng liên tục làm trống trước một luồng khác, thì chỉ mục sử dụng để tìm các nút không có kết quả trong announcemảng sẽ di chuyển về phía trước. Ngay cả khi tất cả các luồng hiện đang cố gắng sắp xếp một nút đã cho liên tục được xử lý trước bởi một luồng khác, cuối cùng tất cả các luồng sẽ cố gắng xâu chuỗi nút đó. Để minh họa, chúng tôi sẽ xây dựng một ví dụ với ba luồng.

Tại điểm bắt đầu, tất cả các phần tử đầu và thông báo của ba luồng được chỉ vào tailnút. Đối lastSequencevới mỗi chủ đề là 0.

Tại thời điểm này, Thread 1 được thực thi với một lời gọi. Nó kiểm tra mảng thông báo cho chuỗi cuối cùng của nó (không), đó là nút mà nó hiện đang được lên lịch để lập chỉ mục. Nó tuần tự nút và nó lastSequenceđược đặt thành 1.

Chủ đề 2 hiện được thực thi với một lời gọi, nó kiểm tra mảng thông báo ở chuỗi cuối cùng của nó (không) và thấy rằng nó không cần trợ giúp và vì vậy cố gắng thực hiện chuỗi gọi đó. Nó thành công và bây giờ nó lastSequenceđược đặt thành 2.

Bây giờ, luồng 3 đã được thực thi và nó cũng thấy rằng nút tại announce[0]đã được sắp xếp theo trình tự và trình tự đó là lệnh gọi riêng của nó. Nó lastSequenceđược thiết lập tới 3.

Bây giờ Thread 1 được gọi lại. Nó kiểm tra mảng thông báo tại chỉ mục 1 và thấy rằng nó đã được giải trình tự. Đồng thời, Thread 2 được gọi. Nó kiểm tra mảng thông báo tại chỉ mục 2 và thấy rằng nó đã được giải trình tự. Cả Thread 1Thread 2 hiện đang cố gắng sắp xếp các nút riêng của chúng. Chủ đề 2 chiến thắng và nó tiếp theo đó là lời mời. Nó lastSequenceđược đặt thành 4. Trong khi đó, chuỗi ba đã được gọi. Nó kiểm tra chỉ mục nó lastSequence(mod 3) và thấy rằng nút tại announce[0]chưa được giải trình tự. Thread 2 một lần nữa được gọi cùng lúc với Thread 1 trong lần thử thứ hai. Chủ đề 1tìm thấy một lời gọi không có kết quả tại announce[1]đó là nút vừa được tạo bởi Thread 2 . Nó cố gắng thực hiện chuỗi yêu cầu của Thread 2 và thành công. Chủ đề 2 tìm thấy nút riêng của nó tại announce[1]và nó đã được giải trình tự. Nó đặt nó lastSequencethành 5. Chủ đề 3 sau đó được gọi và tìm thấy nút mà chủ đề 1 được đặt announce[0]vẫn chưa được giải trình tự và cố gắng thực hiện. Trong khi đó, Thread 2 cũng đã được gọi và pre-empts Thread 3. Nó tuần tự nó là nút và đặt nó lastSequencethành 6.

Chủ đề kém 1 . Mặc dù Thread 3 đang cố gắng sắp xếp nó, cả hai luồng đã liên tục bị cản trở bởi bộ lập lịch. Nhưng tại thời điểm này. Chủ đề 2 hiện cũng đang trỏ đến announce[0](6 mod 3). Tất cả ba luồng được thiết lập để cố gắng sắp xếp thứ tự giống nhau. Bất kể luồng nào thành công, nút tiếp theo sẽ được giải trình tự sẽ là lệnh gọi chờ của luồng 1 tức là nút được tham chiếu bởi announce[0].

Điều này là không thể tránh khỏi. Để các luồng được xử lý trước, các luồng khác phải được sắp xếp các nút và khi chúng làm như vậy, chúng sẽ tiếp tục di chuyển về lastSequencephía trước. Nếu nút của một luồng đã cho liên tục không được tuần tự, cuối cùng tất cả các luồng sẽ được trỏ đến chỉ mục của nó trong mảng thông báo. Không có luồng nào sẽ làm bất cứ điều gì khác cho đến khi nút mà nó đang cố gắng trợ giúp đã được giải trình tự, trường hợp xấu nhất là tất cả các luồng đều trỏ đến cùng một nút không được xử lý. Do đó, thời gian cần thiết để sắp xếp bất kỳ lệnh gọi nào là một hàm của số lượng luồng chứ không phải kích thước của đầu vào.


Bạn có phiền khi đặt một số đoạn trích mã trên pastebin không? Rất nhiều thứ (như danh sách liên kết lockfree) có thể được nói đơn giản như vậy? Thật khó để hiểu toàn bộ câu trả lời của bạn khi có quá nhiều chi tiết. Trong mọi trường hợp, điều này có vẻ đầy hứa hẹn, tôi chắc chắn muốn tìm hiểu những gì đảm bảo nó cung cấp.
VF1

Đây chắc chắn có vẻ như là một triển khai không khóa hợp lệ, nhưng nó thiếu vấn đề cơ bản mà tôi lo lắng. Yêu cầu về tính tuyến tính đòi hỏi phải có "lịch sử hợp lệ", trong trường hợp thực hiện danh sách liên kết, cần một con trỏ previousnexthợp lệ. Duy trì và tạo ra một lịch sử hợp lệ theo cách chờ đợi có vẻ khó khăn.
VF1

@ VF1 Tôi không chắc vấn đề gì không được giải quyết. Tất cả mọi thứ bạn đề cập trong phần còn lại của bình luận được đề cập trong ví dụ tôi đã đưa ra, từ những gì tôi có thể nói.
JimmyJames

Bạn đã từ bỏ tài sản chờ đợi .
VF1

@ VF1 Bạn thấy thế nào?
JimmyJames

0

Câu trả lời trước của tôi không thực sự trả lời đúng câu hỏi nhưng vì OP thấy nó hữu ích, tôi sẽ để nó như vậy. Dựa trên mã trong liên kết trong câu hỏi, đây là nỗ lực của tôi. Tôi đã chỉ thực hiện thử nghiệm thực sự cơ bản về điều này nhưng dường như để tính trung bình đúng. Phản hồi hoan nghênh cho dù điều này là chờ đợi đúng cách.

LƯU Ý : Tôi đã xóa giao diện Universal và biến nó thành một lớp. Có Universal bao gồm các Sequential cũng như là một sự phức tạp không cần thiết nhưng tôi có thể đang thiếu một cái gì đó. Trong lớp trung bình, tôi đã đánh dấu biến trạng thái là volatile. Điều này không cần thiết để làm cho mã hoạt động. Để bảo thủ (một ý tưởng tốt với phân luồng) và ngăn mỗi luồng thực hiện tất cả các phép tính (một lần).

Nhà máy và tuần tự

public interface Sequential<E, S, R>
{ 
  R apply(S priorState);

  S state();

  default boolean isApplied()
  {
    return state() != null;
  }
}

public interface Factory<E, S, R>
{
   S initial();

   Sequential<E, S, R> generate(E input);
}

phổ cập

import java.util.concurrent.ConcurrentLinkedQueue;

public class Universal<I, S, R> 
{
  private final Factory<I, S, R> generator;
  private final ConcurrentLinkedQueue<Sequential<I, S, R>> wfq = new ConcurrentLinkedQueue<>();
  private final ThreadLocal<Sequential<I, S, R>> last = new ThreadLocal<>();

  public Universal(Factory<I, S, R> g)
  { 
    generator = g;
  }

  public R apply(I invocation)
  {
    Sequential<I, S, R> newSequential = generator.generate(invocation);
    wfq.add(newSequential);

    Sequential<I, S, R> last = null;
    S prior = generator.initial(); 

    for (Sequential<I, S, R> i : wfq) {
      if (!i.isApplied() || newSequential == i) {
        R r = i.apply(prior);

        if (i == newSequential) {
          wfq.remove(last.get());
          last.set(newSequential);

          return r;
        }
      }

      prior = i.state();
    }

    throw new IllegalStateException("Houston, we have a problem");
  }
}

Trung bình cộng

public class Average implements Sequential<Integer, Average.State, Double>
{
  private final Integer invocation;
  private volatile State state;

  private Average(Integer invocation)
  {
    this.invocation = invocation;
  }

  @Override
  public Double apply(State prior)
  {
    System.out.println(Thread.currentThread() + " " + invocation + " prior " + prior);

    state = prior.add(invocation);

    return ((double) state.sum)/ state.count;
  }

  @Override
  public State state()
  {
    return state;
  }

  public static class AverageFactory implements Factory<Integer, State, Double> 
  {
    @Override
    public State initial()
    {
      return new State(0, 0);
    }

    @Override
    public Average generate(Integer i)
    {
      return new Average(i);
    }
  }

  public static class State
  {
    private final int sum;
    private final int count;

    private State(int sum, int count)
    {
      this.sum = sum;
      this.count = count;
    }

    State add(int value)
    {
      return new State(sum + value, count + 1);
    }

    @Override
    public String toString()
    {
      return sum + " / " + count;
    }
  }
}

Mã trình diễn

private static final int THREADS = 10;
private static final int SIZE = 50;

public static void main(String... args)
{
  Average.AverageFactory factory = new Average.AverageFactory();

  Universal<Integer, Average.State, Double> universal = new Universal<>(factory);

  for (int i = 0; i < THREADS; i++)
  {
    new Thread(new Test(i * SIZE, universal)).start();
  }
}

static class Test implements Runnable
{
  final int start;
  final Universal<Integer, Average.State, Double> universal;

  Test(int start, Universal<Integer, Average.State, Double> universal)
  {
    this.start = start;
    this.universal = universal;
  }

  @Override
  public void run()
  {
    for (int i = start; i < start + SIZE; i++)
    {
      System.out.println(Thread.currentThread() + " " + i);

      System.out.println(System.nanoTime() + " " + Thread.currentThread() + " " + i + " result " + universal.apply(i));
    }
  }
}

Tôi đã thực hiện một số chỉnh sửa mã khi tôi đăng nó ở đây. Nó sẽ ổn thôi nhưng hãy cho tôi biết nếu bạn có vấn đề với nó.


Bạn không cần phải giữ câu trả lời khác cho tôi (Trước đây tôi đã cập nhật câu hỏi của mình để có bất kỳ kết luận có liên quan nào được rút ra từ đó). Thật không may, câu trả lời này cũng không trả lời được câu hỏi, vì nó không thực sự giải phóng bất kỳ bộ nhớ nào trong wfq, vì vậy bạn vẫn phải duyệt qua toàn bộ lịch sử - thời gian chạy không được cải thiện ngoại trừ bởi một yếu tố không đổi.
VF1

@ Vf1 Thời gian cần thiết để duyệt qua toàn bộ danh sách để kiểm tra xem liệu nó đã được tính toán có phải là rất nhỏ so với thực hiện mỗi phép tính hay không. Bởi vì các trạng thái trước là không bắt buộc, nên có thể loại bỏ các trạng thái ban đầu. Việc kiểm tra rất khó khăn và có thể cần sử dụng bộ sưu tập tùy chỉnh nhưng tôi đã thêm một thay đổi nhỏ.
JimmyJames

@ VF1 Cập nhật thành một triển khai dường như hoạt động với thử nghiệm chữ thảo cơ bản. Tôi không chắc nó an toàn nhưng ngoài đỉnh đầu của tôi, nếu mọi người đều biết các luồng đang hoạt động với nó, nó có thể theo dõi từng luồng và loại bỏ các yếu tố một khi tất cả các luồng đều đi qua chúng một cách an toàn.
JimmyJames

@ VF1 Nhìn vào mã cho ConcurrencyLinkedQueue, phương thức cung cấp có một vòng lặp giống như vòng lặp mà bạn đã tuyên bố khiến câu trả lời khác không phải chờ đợi. Tìm kiếm bình luận "Mất cuộc đua CAS đến một chủ đề khác; đọc lại tiếp theo"
JimmyJames

"Cần có thể loại bỏ các trạng thái ban đầu" - chính xác. Nó nên được , nhưng nó dễ dàng để giới thiệu một cách tinh tế mã mà mất tự do chờ đợi. Một lược đồ theo dõi chủ đề có thể hoạt động. Cuối cùng, tôi không có quyền truy cập vào nguồn CLQ, bạn có phiền liên kết không?
VF1
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.