CountDownLatch so với Semaphore


92

Có lợi thế nào khi sử dụng

java.util.concurrent.CountdownLatch

thay vì

java.util.concurrent.Semaphore ?

Theo như tôi có thể nói các đoạn sau gần như tương đương:

1. Semaphore

final Semaphore sem = new Semaphore(0);
for (int i = 0; i < num_threads; ++ i)
{
  Thread t = new Thread() {
    public void run()
    {
      try
      {
        doStuff();
      }
      finally
      {
        sem.release();
      }
    }
  };
  t.start();
}

sem.acquire(num_threads);

2: CountDownLatch

final CountDownLatch latch = new CountDownLatch(num_threads);
for (int i = 0; i < num_threads; ++ i)
{
  Thread t = new Thread() {
    public void run()
    {
      try
      {
        doStuff();
      }
      finally
      {
        latch.countDown();
      }
    }
  };
  t.start();
}

latch.await();

Ngoại trừ trường hợp # 2, chốt không thể được sử dụng lại và quan trọng hơn là bạn cần biết trước có bao nhiêu luồng sẽ được tạo (hoặc đợi cho đến khi tất cả chúng được bắt đầu trước khi tạo chốt.)

Vậy chốt có thể thích hợp hơn trong tình huống nào?

Câu trả lời:


109

Chốt CountDown thường được sử dụng cho điều ngược lại với ví dụ của bạn. Nói chung, bạn sẽ có nhiều chuỗi chặn trên "await ()", tất cả sẽ bắt đầu đồng thời khi đếm ngược về 0.

final CountDownLatch countdown = new CountDownLatch(1);
for (int i = 0; i < 10; ++ i){
   Thread racecar = new Thread() {    
      public void run()    {
         countdown.await(); //all threads waiting
         System.out.println("Vroom!");
      }
   };
   racecar.start();
}
System.out.println("Go");
countdown.countDown();   //all threads start now!

Bạn cũng có thể sử dụng điều này như một "rào cản" kiểu MPI khiến tất cả các luồng phải chờ các luồng khác bắt kịp đến một điểm nhất định trước khi tiếp tục.

final CountDownLatch countdown = new CountDownLatch(num_thread);
for (int i = 0; i < num_thread; ++ i){
   Thread t= new Thread() {    
      public void run()    {
         doSomething();
         countdown.countDown();
         System.out.printf("Waiting on %d other threads.",countdown.getCount());
         countdown.await();     //waits until everyone reaches this point
         finish();
      }
   };
   t.start();
}

Điều đó đã nói lên rằng, chốt CountDown có thể được sử dụng một cách an toàn theo cách bạn đã chỉ ra trong ví dụ của mình.


1
Cảm ơn. Vì vậy, hai ví dụ của tôi sẽ không tương đương nếu nhiều luồng có thể chờ trên chốt ... trừ khi sem.acquire (num_threads); theo sau là sem.release (num_threads) ;? Tôi nghĩ rằng điều đó sẽ làm cho chúng tương đương trở lại.
finnw

Theo một nghĩa nào đó, có, miễn là mọi luồng được gọi là có được theo sau là phát hành. Nói chính xác là không. Với một chốt, tất cả các chủ đề đủ điều kiện để bắt đầu đồng thời. Với semaphore, chúng lần lượt trở nên đủ điều kiện (có thể dẫn đến lập lịch luồng khác nhau).
James Schek 8/10/08

Tài liệu Java dường như ngụ ý rằng CountdownLatch này rất phù hợp với ví dụ của anh ấy: docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/… . Cụ thể, "Một CountDownLatch được khởi tạo thành N có thể được sử dụng để thực hiện một luồng đợi cho đến khi N luồng đã hoàn thành một số hành động hoặc một số hành động đã được hoàn thành N lần."
Chris Morris

Bạn đúng rồi. Tôi sẽ cập nhật câu trả lời của mình một chút để phản ánh rằng đây là những cách sử dụng phổ biến nhất của CountDownLatch mà tôi đã thấy so với mục đích sử dụng.
James Schek

11
Điều này trả lời câu hỏi Cách sử dụng CountDownLatch thường xuyên nhất là gì? Nó không trả lời cho câu hỏi ban đầu về những ưu điểm / khác biệt của việc sử dụng CountDownLatch trên Semaphore.
Marco Lackovic

67

CountDownLatch được sử dụng để bắt đầu một chuỗi chuỗi và sau đó đợi cho đến khi tất cả chúng hoàn tất (hoặc cho đến khi chúng gọi countDown()một số lần nhất định.

Semaphore được sử dụng để kiểm soát số lượng các luồng đồng thời đang sử dụng một tài nguyên. Tài nguyên đó có thể là một cái gì đó giống như một tệp hoặc có thể là cpu bằng cách giới hạn số luồng thực thi. Số lượng trên Semaphore có thể tăng và giảm khi các luồng khác nhau gọi acquire()release().

Trong ví dụ của bạn, về cơ bản bạn đang sử dụng Semaphore như một loại Đếm Latch UP . Cho rằng mục đích của bạn là đợi tất cả các chuỗi kết thúc, việc sử dụng hàm CountdownLatchlàm cho ý định của bạn rõ ràng hơn.


22

Tóm tắt ngắn gọn:

  1. SemaphoreCountDownLatch phục vụ các mục đích khác nhau.

  2. Sử dụng Semaphore để kiểm soát luồng truy cập tài nguyên.

  3. Sử dụng CountDownLatch để đợi hoàn thành tất cả các chuỗi

Định nghĩa Semaphore từ javadocs:

Một Semaphore duy trì một bộ giấy phép. Mỗi khối lệnh get () nếu cần thiết cho đến khi có giấy phép và sau đó lấy nó. Mỗi bản phát hành () thêm một giấy phép, có khả năng phát hành một trình thu thập chặn.

Tuy nhiên, không có đối tượng giấy phép thực tế nào được sử dụng; các Semaphore chỉ giữ một đếm số có sẵn và hoạt động cho phù hợp.

Làm thế nào nó hoạt động ?

Cột được sử dụng để kiểm soát số lượng đề đồng thời đang sử dụng một nguồn lực resource.That có thể là cái gì đó giống như một dữ liệu chia sẻ, hoặc một khối mã ( phần quan trọng ) hoặc bất kỳ tập tin.

Số lượng trên Semaphore có thể lên xuống khi các luồng khác nhau gọi acquire() và release(). Nhưng tại bất kỳ thời điểm nào, bạn không thể có số lượng chủ đề lớn hơn số lượng Semaphore.

Các trường hợp sử dụng Semaphore:

  1. Hạn chế quyền truy cập đồng thời vào đĩa (điều này có thể làm giảm hiệu suất do tìm kiếm đĩa cạnh tranh)
  2. Giới hạn tạo luồng
  3. Tổng hợp / giới hạn kết nối JDBC
  4. Điều chỉnh kết nối mạng
  5. Điều chỉnh CPU hoặc các tác vụ chuyên sâu về bộ nhớ

Hãy xem bài viết này để biết cách sử dụng semaphore.

Định nghĩa CountDownLatch từ javadocs:

Hỗ trợ đồng bộ hóa cho phép một hoặc nhiều luồng đợi cho đến khi một tập hợp các thao tác được thực hiện trong các luồng khác hoàn tất.

Làm thế nào nó hoạt động?

CountDownLatch hoạt động bằng cách khởi tạo bộ đếm với số luồng, số lượng này sẽ giảm đi mỗi khi một luồng hoàn thành việc thực thi. Khi số đếm về 0, điều đó có nghĩa là tất cả các luồng đã hoàn thành quá trình thực thi của chúng và luồng chờ trên chốt sẽ tiếp tục thực thi.

CountDownLatch Các trường hợp sử dụng:

  1. Đạt được song song tối đa: Đôi khi chúng ta muốn bắt đầu một số luồng cùng một lúc để đạt được song song tối đa
  2. Chờ N chuỗi hoàn thành trước khi bắt đầu thực thi
  3. Phát hiện bế tắc.

Hãy xem bài viết này để hiểu rõ khái niệm CountDownLatch.

Hãy xem Fork Join Pool tại bài viết này . Nó có một số điểm tương đồng với CountDownLatch .


7

Giả sử bạn bước vào cửa hàng chơi gôn chuyên nghiệp, hy vọng tìm được một bộ tứ,

Khi bạn đứng xếp hàng để nhận thời gian phát bóng từ một trong những người phục vụ tại cửa hàng chuyên nghiệp, về cơ bản bạn đã gọi proshopVendorSemaphore.acquire(), khi bạn có thời gian phát bóng, bạn đã gọi. Lưu proshopVendorSemaphore.release()ý: bất kỳ người phục vụ miễn phí nào cũng có thể phục vụ bạn, tức là tài nguyên được chia sẻ.

Bây giờ bạn bước đến phần khởi động, anh ta bắt đầu a CountDownLatch(4)và gọi điện await()để đợi người khác, về phần bạn, tức là bạn đã gọi là đăng ký CountDownLatch. countDown()và phần còn lại của bộ tứ cũng vậy. Khi tất cả đến nơi, người khởi động sẽ tiếp tục ( await()cuộc gọi trả về)

Bây giờ, sau chín lỗ khi mỗi người trong số các bạn nghỉ giải lao, theo giả thuyết cho phép tham gia lại người khởi động, anh ta sử dụng 'mới' CountDownLatch(4)để phát bóng ở Lỗ 10, cùng một lượt chờ / đồng bộ với Lỗ 1.

Tuy nhiên, nếu người khởi động sử dụng một CyclicBarrierđể bắt đầu, anh ta có thể đã đặt lại cùng một phiên bản ở Lỗ 10 thay vì một chốt thứ hai, sử dụng & ném.


1
Tôi không chắc mình hiểu câu trả lời của bạn, nhưng nếu bạn đang cố gắng mô tả cách hoạt động của CountdownLatch và Semaphore, thì đó không phải là chủ đề của câu hỏi.
finnw

10
Thật không may, tôi không biết gì về golf.
người mang nhẫn

nhưng nội dung khởi động cũng có thể được thực hiện với .acquire (người chơi) và tăng số lượng relesed khi phát hành. đồng hồ đếm ngược dường như chỉ có ít chức năng hơn và không có khả năng tái sử dụng.
Lassi Kinnunen

1

Nhìn vào nguồn tự do có sẵn, không có phép thuật nào trong việc thực hiện hai lớp, vì vậy hiệu suất của chúng phải giống nhau. Chọn một trong những làm cho ý định của bạn rõ ràng hơn.


0

CountdownLatchlàm cho các chủ đề đợi trên await()phương thức, cho đến khi số đếm đạt đến 0. Vì vậy, có thể bạn muốn tất cả các chủ đề của mình đợi cho đến khi 3 lần gọi thứ gì đó, sau đó tất cả các chủ đề có thể đi. Một Latchthường không thể được đặt lại.

A Semaphorecho phép các luồng truy xuất giấy phép, ngăn quá nhiều luồng thực thi cùng một lúc, chặn nếu nó không thể lấy (các) giấy phép mà nó yêu cầu để tiếp tục. Giấy phép có thể được trả lại Semaphorecho phép các chuỗi chờ khác tiếp tục.

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.