Cách sử dụng chính của hàm gain () và nó khác với join () và ngắt () như thế nào?


106

Tôi hơi bối rối về việc sử dụng yield()phương thức trong Java, cụ thể là trong đoạn mã ví dụ bên dưới. Tôi cũng đã đọc rằng hàm yi () được 'sử dụng để ngăn chặn việc thực thi một luồng'.

Câu hỏi của tôi là:

  1. Tôi tin rằng đoạn mã dưới đây cho ra kết quả giống nhau cả khi sử dụng yield()và khi không sử dụng. Điều này có chính xác?

  2. Trên thực tế, công dụng chính của nó là yield()gì?

  3. Ở những điểm nào yield()khác với phương thức join()interrupt()phương pháp?

Ví dụ mã:

public class MyRunnable implements Runnable {

   public static void main(String[] args) {
      Thread t = new Thread(new MyRunnable());
      t.start();

      for(int i=0; i<5; i++) {
          System.out.println("Inside main");
      }
   }

   public void run() {
      for(int i=0; i<5; i++) {
          System.out.println("Inside run");
          Thread.yield();
      }
   }
}

Tôi nhận được cùng một đầu ra bằng cách sử dụng mã trên cả có và không sử dụng yield():

Inside main
Inside main
Inside main
Inside main
Inside main
Inside run
Inside run
Inside run
Inside run
Inside run

Sự cạn kiệt này nên được đóng lại vì quá rộng .
Raedwald

Không. Nó không trả về cùng một kết quả khi bạn có yield()và không. khi bạn có i lớn hơn 5, bạn có thể thấy tác dụng của yield()phương pháp.
lakshman

Câu trả lời:


97

Nguồn: http://www.javamex.com/tutorials/threads/yield.shtml

các cửa sổ

Trong triển khai Hotspot, cách thức Thread.yield()hoạt động đã thay đổi giữa Java 5 và Java 6.

Trong Java 5, Thread.yield()gọi lệnh gọi API Windows Sleep(0). Điều này có tác dụng đặc biệt là xóa lượng tử của luồng hiện tại và đưa nó vào cuối hàng đợi cho mức độ ưu tiên của nó . Nói cách khác, tất cả các luồng có thể chạy được có cùng mức độ ưu tiên (và những luồng có mức độ ưu tiên cao hơn) sẽ có cơ hội chạy trước khi luồng có năng suất có thời gian CPU tiếp theo. Cuối cùng khi được lên lịch lại, nó sẽ quay trở lại với một lượng tử đầy đủ , nhưng không "mang theo" bất kỳ lượng tử còn lại nào kể từ thời điểm tạo ra. Hành vi này hơi khác một chút so với chế độ ngủ khác không, trong đó chuỗi ngủ thường mất 1 giá trị lượng tử (thực tế là 1/3 của tích tắc 10 hoặc 15ms).

Trong Java 6, hành vi này đã được thay đổi. Hotspot VM hiện triển khai Thread.yield()bằng SwitchToThread()lệnh gọi API Windows . Lời gọi này làm cho luồng hiện tại từ bỏ biểu đồ thời gian hiện tại của nó , nhưng không phải toàn bộ lượng tử của nó. Điều này có nghĩa là tùy thuộc vào mức độ ưu tiên của các luồng khác, luồng năng suất có thể được lên lịch trở lại trong một khoảng thời gian ngắt sau đó . (Xem phần lập lịch luồng để biết thêm thông tin về thời gian.)

Linux

Trong Linux, Hotspot chỉ đơn giản là cuộc gọi sched_yield(). Hậu quả của cuộc gọi này hơi khác một chút và có thể nghiêm trọng hơn so với trong Windows:

  • một luồng năng suất sẽ không nhận được một phần CPU khác cho đến khi tất cả các luồng khác đã có một phần CPU ;
  • (ít nhất là trong kernel 2.6.8 trở đi), thực tế là luồng đã mang lại kết quả được xem xét một cách ngầm định bởi kinh nghiệm của người lập lịch về phân bổ CPU gần đây của nó - do đó, một cách ngầm định, một luồng đã mang lại có thể được cấp nhiều CPU hơn khi được lập lịch trong tương lai.

(Xem phần lập lịch luồng để biết thêm chi tiết về các ưu tiên và thuật toán lập lịch.)

Sử dụng khi yield()nào?

Tôi sẽ nói thực tế là không bao giờ . Hành vi của nó không được xác định tiêu chuẩn và thường có những cách tốt hơn để thực hiện các tác vụ mà bạn có thể muốn thực hiện với lợi nhuận ():

  • nếu bạn đang cố gắng chỉ sử dụng một phần CPU , bạn có thể thực hiện việc này theo cách dễ kiểm soát hơn bằng cách ước tính lượng CPU mà luồng đã sử dụng trong phần xử lý cuối cùng của nó, sau đó ngủ một khoảng thời gian để bù lại: xem các ) ngủ ( phương pháp;
  • nếu bạn đang đợi một quy trình hoặc tài nguyên hoàn thành hoặc khả dụng, có nhiều cách hiệu quả hơn để thực hiện điều này, chẳng hạn như bằng cách sử dụng join () để đợi một chuỗi khác hoàn thành, sử dụng cơ chế chờ / thông báo để cho phép một chuỗi để báo hiệu cho người khác biết rằng một nhiệm vụ đã hoàn thành, hoặc lý tưởng nhất là bằng cách sử dụng một trong các cấu trúc đồng thời của Java 5 như Semaphore hoặc hàng đợi chặn .

18
"còn lại lượng tử", "toàn bộ lượng tử" - ở đâu đó trên đường đi ai đó quên những gì từ "lượng tử" có nghĩa là
kbolino

@kbolino Lượng tử là nguyên tử mới.
Evgeni Sergeev

2
@kbolino - ... tiếng latin: "as much as", "how much" . Tôi không thấy điều này mâu thuẫn với cách sử dụng ở trên như thế nào. Từ đơn giản có nghĩa là một số lượng được mô tả của một cái gì đó, vì vậy việc chia nó thành các phần đã sử dụng và phần còn lại có vẻ hoàn toàn hợp lý với tôi.
Periata Breatta

@PeriataBreatta Tôi đoán nó có ý nghĩa hơn nếu bạn quen thuộc với từ bên ngoài vật lý. Định nghĩa vật lý là định nghĩa duy nhất tôi biết.
kbolino

Tôi đã đặt tiền thưởng cho câu hỏi này để câu trả lời này được cập nhật cho 7, 8, 9. Chỉnh sửa nó với thông tin hiện tại về 7,8 và 8 và bạn sẽ nhận được tiền thưởng.

40

Tôi thấy câu hỏi đã được kích hoạt lại với một khoản tiền thưởng, bây giờ hỏi mục đích thực tế yieldlà gì. Tôi sẽ đưa ra một ví dụ từ kinh nghiệm của tôi.

Như chúng ta đã biết, yieldbuộc luồng gọi từ bỏ bộ xử lý mà nó đang chạy để luồng khác có thể được lên lịch chạy. Điều này rất hữu ích khi luồng hiện tại đã hoàn thành công việc nhưng muốn nhanh chóng quay trở lại phía trước hàng đợi và kiểm tra xem một số điều kiện đã thay đổi hay chưa. Điều này khác với một biến điều kiện như thế nào? yieldcho phép luồng trở về trạng thái đang chạy nhanh hơn nhiều. Khi chờ một biến điều kiện, luồng bị tạm dừng và cần đợi một luồng khác báo hiệu rằng nó sẽ tiếp tục.yieldvề cơ bản nói rằng "cho phép một luồng khác chạy, nhưng hãy cho phép tôi trở lại làm việc rất sớm khi tôi mong đợi điều gì đó sẽ thay đổi trong trạng thái của tôi rất nhanh". Điều này gợi ý về việc quay bận rộn, trong đó một điều kiện có thể thay đổi nhanh chóng nhưng việc tạm dừng chỉ sẽ gây ra một hiệu suất lớn.

Nhưng lảm nhảm đủ rồi, đây là một ví dụ cụ thể: mô hình song song mặt sóng. Một ví dụ cơ bản của vấn đề này là tính toán các "đảo" riêng lẻ của 1s trong một mảng hai chiều chứa đầy 0s và 1s. "Đảo" là một nhóm các ô liền kề với nhau theo chiều dọc hoặc chiều ngang:

1 0 0 0
1 1 0 0
0 0 0 1
0 0 1 1
0 0 1 1

Ở đây chúng ta có hai hòn đảo 1s: trên cùng bên trái và dưới cùng bên phải.

Một giải pháp đơn giản là thực hiện chuyển đầu tiên trên toàn bộ mảng và thay thế các giá trị 1 bằng bộ đếm tăng dần sao cho cuối mỗi giá trị 1 được thay thế bằng số thứ tự của nó theo thứ tự chính của hàng:

1 0 0 0
2 3 0 0
0 0 0 4
0 0 5 6
0 0 7 8

Trong bước tiếp theo, mỗi giá trị được thay thế bằng giá trị nhỏ nhất giữa chính nó và các giá trị lân cận của nó:

1 0 0 0
1 1 0 0
0 0 0 4
0 0 4 4
0 0 4 4

Bây giờ chúng ta có thể dễ dàng xác định rằng chúng ta có hai hòn đảo.

Phần chúng tôi muốn chạy song song là bước chúng tôi tính toán các giá trị tối thiểu. Không đi vào quá nhiều chi tiết, mỗi luồng nhận được các hàng theo cách xen kẽ và dựa vào các giá trị được tính toán bởi luồng xử lý hàng ở trên. Do đó, mỗi luồng cần hơi trễ so với luồng xử lý của dòng trước đó, nhưng cũng phải theo kịp trong thời gian hợp lý. Chi tiết hơn và cách triển khai được tôi trình bày trong tài liệu này . Lưu ý cách sử dụng của sleep(0)nó nhiều hơn hoặc ít hơn C tương đương với yield.

Trong trường hợp này yieldđược sử dụng để buộc mỗi luồng lần lượt tạm dừng, nhưng vì luồng xử lý hàng liền kề sẽ tiến rất nhanh trong thời gian chờ đợi, một biến điều kiện sẽ chứng minh một lựa chọn tai hại.

Như bạn có thể thấy, yieldlà một tối ưu hóa khá nhỏ. Sử dụng nó không đúng chỗ, ví dụ như chờ đợi một điều kiện ít khi thay đổi, sẽ gây ra việc sử dụng CPU quá mức.

Xin lỗi vì những lời nói lảm nhảm dài dòng, hy vọng tôi đã nói rõ.


1
IIUC những gì bạn trình bày trong tài liệu, ý tưởng là trong trường hợp này, sẽ hiệu quả hơn khi chờ đợi, gọi yieldkhi điều kiện không được thỏa mãn để tạo cơ hội cho các luồng khác tiến hành tính toán, thay vì sử dụng nhiều- nguyên thủy đồng bộ hóa mức, phải không?
Petr Pudlák

3
@Petr Pudlák: Vâng. Tôi đã đánh giá điều này so với sử dụng tín hiệu luồng và sự khác biệt về hiệu suất là rất lớn trong trường hợp này. Vì điều kiện có thể trở thành đúng rất nhanh (đây là vấn đề quan trọng), các biến điều kiện quá chậm khi luồng được hệ điều hành giữ lại, thay vì từ bỏ CPU trong một thời gian rất ngắn yield.
Tudor

@Tudor giải thích tuyệt vời!
Nhà phát triển Marius Žilėnas

1
"Lưu ý việc sử dụng sleep (0) ít nhiều tương đương với C của năng suất." .. tốt, nếu bạn muốn sleep (0) với java, tại sao bạn không sử dụng nó? Thread.sleep () là một thứ đã tồn tại. Tôi không chắc liệu câu trả lời này có cung cấp lý do tại sao người ta sẽ sử dụng Thread.yield () thay vì Thread.sleep (0); Cũng có một chủ đề hiện tại giải thích tại sao chúng khác nhau.
eis

@eis: Thread.sleep (0) so với Thread.yield () nằm ngoài phạm vi của câu trả lời này. Tôi chỉ đề cập đến Thread.sleep (0) cho những người đang tìm kiếm một từ tương đương gần bằng C. Câu hỏi là về cách sử dụng của Thread.yield ().
Tudor

12

Về sự khác nhau giữa yield(), interrupt()join()- nói chung, không chỉ trong Java:

  1. nhượng bộ : Theo nghĩa đen, 'nhường' có nghĩa là buông bỏ, từ bỏ, đầu hàng. Một luồng năng suất cho hệ điều hành (hoặc máy ảo, hoặc không) nó sẵn sàng để các luồng khác được lên lịch thay cho nó. Điều này cho thấy nó không làm điều gì đó quá quan trọng. Tuy nhiên, đó chỉ là một gợi ý và không được đảm bảo sẽ có bất kỳ tác dụng nào.
  2. tham gia : Khi nhiều luồng 'tham gia' trên một số xử lý hoặc mã thông báo hoặc thực thể, tất cả chúng sẽ đợi cho đến khi tất cả các luồng có liên quan khác hoàn tất thực thi (hoàn toàn hoặc tối đa kết nối tương ứng của riêng chúng). Điều đó có nghĩa là một loạt các chủ đề đã hoàn thành nhiệm vụ của chúng. Sau đó, mỗi một trong những luồng này có thể được lên lịch để tiếp tục công việc khác, có thể giả sử rằng tất cả các nhiệm vụ đó đã thực sự hoàn thành. (Đừng nhầm với SQL Joins!)
  3. gián đoạn : Được sử dụng bởi một chuỗi để 'chọc' một chuỗi khác đang ngủ, hoặc đang chờ, hoặc đang tham gia - để nó được lên lịch tiếp tục chạy lại, có lẽ với một dấu hiệu là nó đã bị gián đoạn. (Không nên nhầm lẫn với ngắt phần cứng!)

Đối với Java cụ thể, hãy xem

  1. Tham gia:

    Làm thế nào để sử dụng Thread.join?(đây trên StackOverflow)

    Khi nào tham gia chủ đề?

  2. Năng suất:

  3. Làm gián đoạn:

    Thread.interrupt () có ác không? (đây trên StackOverflow)


Ý bạn là gì khi tham gia một tay cầm hoặc mã thông báo? Các phương thức wait () và allow () nằm trên Object, cho phép người dùng đợi trên bất kỳ Object tùy ý nào. Nhưng join () có vẻ ít trừu tượng hơn và cần được gọi trên Thread cụ thể mà bạn muốn kết thúc trước khi tiếp tục ... phải không?
spaaarky21

@ spaaarky21: Ý tôi là nói chung, không nhất thiết phải bằng Java. Ngoài ra, a wait()không phải là một phép nối, nó nói về một khóa đối tượng mà chuỗi gọi đang cố gắng lấy - nó đợi cho đến khi khóa được giải phóng bởi những người khác và đã được chuỗi đó mua lại. Điều chỉnh câu trả lời của tôi cho phù hợp.
einpoklum

10

Đầu tiên, mô tả thực tế là

Làm cho đối tượng luồng đang thực thi tạm thời tạm dừng và cho phép các luồng khác thực thi.

Bây giờ, rất có thể luồng chính của bạn sẽ thực hiện vòng lặp năm lần trước khi runphương thức của luồng mới được thực thi, vì vậy tất cả các lệnh gọi yieldsẽ chỉ xảy ra sau khi vòng lặp trong luồng chính được thực thi.

join sẽ dừng luồng hiện tại cho đến khi luồng được gọi với join() được thực thi xong.

interruptsẽ ngắt luồng nó đang được gọi, gây ra InterruptException .

yield cho phép chuyển ngữ cảnh sang các luồng khác, vì vậy luồng này sẽ không tiêu tốn toàn bộ mức sử dụng CPU của quá trình.


+1. Cũng lưu ý rằng sau khi gọi hàm yi (), vẫn không có gì đảm bảo rằng chủ đề tương tự sẽ không được chọn để thực thi lại, với một nhóm các chủ đề ưu tiên bằng nhau.
Andrew Fielden

Tuy nhiên, SwitchToThread()cuộc gọi tốt hơn Sleep (0) và đây sẽ là một lỗi trong Java :)
Петър Петров

4

(Các) câu trả lời hiện tại đã lỗi thời và cần được sửa đổi nếu có những thay đổi gần đây.

Không có sự khác biệt thực tếThread.yield() giữa các phiên bản Java kể từ 6 đến 9.

TL; DR;

Kết luận dựa trên mã nguồn OpenJDK ( http://hg.openjdk.java.net/ ).

Nếu không tính đến hỗ trợ HotSpot của các đầu dò USDT (thông tin truy tìm hệ thống được mô tả trong hướng dẫn dtrace ) và thuộc tính JVM ConvertYieldToSleepthì mã nguồn của yield()gần như giống nhau. Xem giải thích bên dưới.

Java 9 :

Thread.yield()gọi phương thức dành riêng cho hệ điều hành os::naked_yield():
Trên Linux:

void os::naked_yield() {
    sched_yield();
}

Trên Windows:

void os::naked_yield() {
    SwitchToThread();
}

Java 8 trở về trước:

Thread.yield()gọi phương thức dành riêng cho hệ điều hành os::yield():
Trên Linux:

void os::yield() {
    sched_yield();
}

Trên Windows:

void os::yield() {  os::NakedYield(); }

Như bạn thấy, Thread.yeald()trên Linux là giống hệt nhau cho tất cả các phiên bản Java.
Hãy xem Windows của os::NakedYield()JDK 8:

os::YieldResult os::NakedYield() {
    // Use either SwitchToThread() or Sleep(0)
    // Consider passing back the return value from SwitchToThread().
    if (os::Kernel32Dll::SwitchToThreadAvailable()) {
        return SwitchToThread() ? os::YIELD_SWITCHED : os::YIELD_NONEREADY ;
    } else {
        Sleep(0);
    }
    return os::YIELD_UNKNOWN ;
}

Sự khác biệt giữa Java 9 và Java 8 trong kiểm tra bổ sung về sự tồn tại của SwitchToThread()phương thức Win32 API . Mã nguồn tương tự hiện có cho Java 6.
Mã nguồn của os::NakedYield()JDK 7 hơi khác một chút nhưng nó có cùng hành vi:

    os::YieldResult os::NakedYield() {
    // Use either SwitchToThread() or Sleep(0)
    // Consider passing back the return value from SwitchToThread().
    // We use GetProcAddress() as ancient Win9X versions of windows doen't support SwitchToThread.
    // In that case we revert to Sleep(0).
    static volatile STTSignature stt = (STTSignature) 1 ;

    if (stt == ((STTSignature) 1)) {
        stt = (STTSignature) ::GetProcAddress (LoadLibrary ("Kernel32.dll"), "SwitchToThread") ;
        // It's OK if threads race during initialization as the operation above is idempotent.
    }
    if (stt != NULL) {
        return (*stt)() ? os::YIELD_SWITCHED : os::YIELD_NONEREADY ;
    } else {
        Sleep (0) ;
    }
    return os::YIELD_UNKNOWN ;
}

Kiểm tra bổ sung đã bị bỏ do SwitchToThread()phương pháp có sẵn kể từ Windows XP và Windows Server 2003 (xem ghi chú msdn ).


2

Trên thực tế, công dụng chính của hàm gain () là gì?

Yield gợi ý cho CPU rằng bạn có thể dừng luồng hiện tại và bắt đầu thực hiện các luồng có mức độ ưu tiên cao hơn. Nói cách khác, việc gán giá trị ưu tiên thấp cho luồng hiện tại để nhường chỗ cho các luồng quan trọng hơn.

Tôi tin rằng đoạn mã dưới đây cho ra kết quả giống nhau cả khi sử dụng hàm yi () và khi không sử dụng. Điều này có chính xác?

KHÔNG, cả hai sẽ tạo ra kết quả khác nhau. Nếu không có kết quả (), một khi luồng được kiểm soát, nó sẽ thực hiện vòng lặp 'Inside run' trong một lần. Tuy nhiên, với một kết quả (), một khi luồng được kiểm soát, nó sẽ in 'Inside run' một lần và sau đó sẽ chuyển giao quyền kiểm soát cho luồng khác nếu có. Nếu không có chuỗi nào đang chờ xử lý, chuỗi này sẽ được tiếp tục lại. Vì vậy, mỗi khi "Inside run" được thực thi, nó sẽ tìm kiếm các luồng khác để thực thi và nếu không có luồng nào, luồng hiện tại sẽ tiếp tục thực thi.

Lợi nhuận () khác với phương thức nối () và ngắt () ở những điểm nào?

output () là để nhường chỗ cho các luồng quan trọng khác, join () là để chờ một luồng khác hoàn thành việc thực thi của nó và ngắt () là để ngắt một luồng hiện đang thực thi để làm việc khác.


Chỉ muốn xác nhận xem tuyên bố này có đúng Without a yield(), once the thread gets control it will execute the 'Inside run' loop in one gokhông? Vui lòng làm rõ.
Abdullah Khan

0

Thread.yield()khiến luồng chuyển từ trạng thái "Đang chạy" sang trạng thái "Có thể chạy được". Lưu ý: Nó không làm cho chuỗi chuyển sang trạng thái "Đang chờ".


@PJMeisch, Không có RUNNINGtrạng thái nào cho các java.lang.Threadphiên bản. Nhưng điều đó không loại trừ trạng thái "đang chạy" gốc cho luồng gốc mà một Threadphiên bản là proxy.
Solomon Slow

-1

Thread.yield ()

Khi chúng ta gọi phương thức Thread.yield (), bộ lập lịch luồng giữ luồng hiện đang chạy ở trạng thái Runnable và chọn một luồng khác có mức độ ưu tiên tương đương hoặc mức độ ưu tiên cao hơn. Nếu không có luồng nào có mức độ ưu tiên cao hơn và bằng nhau thì nó sẽ lên lịch lại luồng gọi yif (). Hãy nhớ rằng phương pháp lợi nhuận không làm cho chuỗi chuyển sang trạng thái Chờ hoặc Bị chặn. Nó chỉ có thể tạo một luồng từ Trạng thái đang chạy sang Trạng thái có thể chạy được.

tham gia()

Khi tham gia được gọi bởi một cá thể của luồng, luồng này sẽ thông báo cho luồng hiện đang thực thi đợi cho đến khi luồng Kết nối hoàn thành. Tham gia được sử dụng trong các tình huống khi một nhiệm vụ cần được hoàn thành trước khi nhiệm vụ hiện tại kết thúc.


-4

công dụng chính của output () là để tạm dừng ứng dụng đa luồng.

tất cả sự khác biệt của các phương thức này là yix () đặt luồng ở trạng thái giữ trong khi thực thi một luồng khác và quay trở lại sau khi hoàn thành luồng đó, join () sẽ đưa phần đầu của luồng cùng thực thi cho đến khi kết thúc và của một luồng khác để chạy sau khi luồng đó có kết thúc, ngắt () sẽ dừng việc thực thi một luồng trong một thời gian.


Cảm ơn bạn vì câu trả lời. Tuy nhiên, nó chỉ lặp lại những gì các câu trả lời khác đã mô tả chi tiết. Tôi đang cung cấp tiền thưởng cho các trường hợp sử dụng thích hợp yieldnên được sử dụng.
Petr Pudlák
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.