Do thức dậy giả trong Java thực sự xảy ra?


208

Thấy nhiều khóa liên quan câu hỏi và (hầu như) luôn luôn tìm ra 'loop vì wakeups giả mạo' về 1 Tôi ngạc nhiên, đã ai có kinh nghiệm loại đó của một wakeup (giả sử một môi trường phần cứng / phần mềm khá chẳng hạn)?

Tôi biết thuật ngữ 'giả mạo' có nghĩa là không có lý do rõ ràng nhưng lý do cho sự kiện như vậy là gì?

( 1 Lưu ý: Tôi không đặt câu hỏi về thực hành lặp.)

Chỉnh sửa: Một câu hỏi trợ giúp (cho những người thích mẫu mã):

Nếu tôi có chương trình sau, và tôi chạy nó:

public class Spurious {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition cond = lock.newCondition();
        lock.lock();
        try {
            try {
                cond.await();
                System.out.println("Spurious wakeup!");
            } catch (InterruptedException ex) {
                System.out.println("Just a regular interrupt.");
            }
        } finally {
            lock.unlock();
        }
    }
}

Tôi có thể làm gì để đánh thức điều này awaitmột cách giả tạo mà không phải chờ đợi một sự kiện ngẫu nhiên mãi mãi?


1
Đối với các JVM chạy trên các hệ thống POSIX và sử dụng pthread_cond_wait()câu hỏi thực sự là "Tại sao pthread_cond_wait có đánh thức giả?" .
Dòng chảy

Câu trả lời:


204

Bài viết trên Wikipedia về đánh thức giả có nội dung này:

Các pthread_cond_wait()chức năng trong Linux được thực hiện bằng cách sử dụng futexcuộc gọi hệ thống. Mỗi cuộc gọi hệ thống chặn trên Linux trở lại đột ngột EINTRkhi quá trình nhận được tín hiệu. ... pthread_cond_wait()không thể khởi động lại sự chờ đợi vì nó có thể bỏ lỡ một sự thức tỉnh thực sự trong thời gian ngắn ngoài futexcuộc gọi hệ thống. Điều kiện cuộc đua này chỉ có thể tránh được bởi người gọi kiểm tra bất biến. Do đó, tín hiệu POSIX sẽ tạo ra một đánh thức giả.

Tóm tắt : Nếu một quy trình Linux được báo hiệu, các luồng chờ của nó sẽ được hưởng một thức dậy giả đẹp, nóng .

Tôi mua nó. Đó là một viên thuốc dễ nuốt hơn so với lý do "nó cho hiệu suất" mơ hồ thường được đưa ra.


13
Giải thích rõ hơn tại đây: stackoverflow.com/questions/1461913/
Ấn

3
Việc bỏ chặn EINTR này đúng với tất cả các lệnh gọi hệ thống chặn trong các hệ thống dẫn xuất Unix. Điều này làm cho kernel rất đơn giản, nhưng các lập trình viên ứng dụng đã mua gánh nặng.
Tim Williscroft

2
Tôi nghĩ pthread_cond_wait () và bạn bè không thể trả lại EINTR, nhưng trả về số 0 nếu thức dậy một cách giả tạo? Từ: pubs.opengroup.org/onlinepub/7908799/xsh/ triệt "Các chức năng này sẽ không trả về mã lỗi của [EINTR]."
gubby

2
@jgubby Đúng vậy. Trả futex()về cuộc gọi cơ bản EINTR, nhưng giá trị trả lại đó không tăng lên cấp độ tiếp theo. Do đó, người gọi pthread phải kiểm tra bất biến. Điều họ đang nói là khi pthread_cond_wait()trở về, bạn phải kiểm tra lại tình trạng vòng lặp của mình (bất biến), bởi vì sự chờ đợi có thể đã bị đánh thức một cách kỳ lạ. Nhận tín hiệu trong khi gọi hệ thống là một nguyên nhân có thể, nhưng đó không phải là nguyên nhân duy nhất.
John Kugelman

1
Có lẽ, pthreadthư viện có thể cung cấp bất biến riêng và logic kiểm tra riêng của mình để loại bỏ các đánh thức giả, thay vì chuyển trách nhiệm đó cho người dùng. Điều đó (có lẽ) có tác động hiệu suất được yêu cầu.

22

Tôi có một hệ thống sản xuất thể hiện hành vi này. Một chuỗi chờ trên một tín hiệu rằng có một thông báo trong hàng đợi. Trong thời gian bận rộn, có tới 20% số lần đánh thức là giả mạo (tức là khi nó thức dậy, không có gì trong hàng đợi). Chủ đề này là người tiêu dùng duy nhất của các tin nhắn. Nó chạy trên hộp xử lý Linux SLES-10 8 và được xây dựng với GCC 4.1.2. Các tin nhắn đến từ một nguồn bên ngoài và được xử lý không đồng bộ vì có vấn đề nếu hệ thống của tôi không đọc chúng đủ nhanh.


15

Để trả lời câu hỏi trong tiêu đề - Có! nó đã xảy ra. Bài viết Wiki đề cập rất nhiều về việc đánh thức giả mạo một lời giải thích hay cho điều tương tự mà tôi đã gặp là như sau -

Chỉ cần nghĩ về nó ... giống như bất kỳ mã nào, bộ lập lịch luồng có thể bị mất điện tạm thời do có điều gì đó bất thường xảy ra trong phần cứng / phần mềm cơ bản. Tất nhiên, cần thận trọng để điều này xảy ra càng hiếm càng tốt, nhưng vì không có phần mềm mạnh mẽ nào 100% nên có lý khi cho rằng điều này có thể xảy ra và quan tâm đến sự phục hồi duyên dáng trong trường hợp nếu trình lập lịch phát hiện ra điều này (ví dụ bằng cách quan sát nhịp tim bị mất).

Bây giờ, làm thế nào bộ lập lịch có thể phục hồi, có tính đến việc trong thời gian mất điện, nó có thể bỏ lỡ một số tín hiệu nhằm thông báo các chuỗi chờ? Nếu bộ lập lịch không làm gì, các chủ đề "không may mắn" được đề cập sẽ chỉ bị treo, chờ đợi mãi mãi - để tránh điều này, bộ lập lịch chỉ đơn giản là gửi tín hiệu đến tất cả các chuỗi chờ.

Điều này làm cho nó cần thiết để thiết lập một "hợp đồng" rằng chuỗi chờ có thể được thông báo mà không có lý do. Nói chính xác, sẽ có một lý do - tắt lịch trình - nhưng vì luồng được thiết kế (vì một lý do chính đáng) không để ý đến các chi tiết triển khai nội bộ của lịch trình, lý do này có thể tốt hơn để trình bày dưới dạng "giả".

Tôi đã đọc câu trả lời này từ Nguồn và thấy nó đủ hợp lý. Cũng đọc

Đánh thức giả trong Java và làm thế nào để tránh chúng .

PS: Liên kết trên là blog cá nhân của tôi có thêm thông tin chi tiết về đánh thức giả.


9

Cameron Purdy đã viết một bài đăng trên blog một thời gian trước về việc bị ảnh hưởng bởi vấn đề đánh thức giả. Vì vậy, có, nó xảy ra

Tôi đoán nó nằm trong thông số kỹ thuật (như một khả năng) vì những hạn chế của một số nền tảng mà Java được triển khai trên? mặc dù tôi có thể sai


Tôi đọc bài đăng và cho tôi một ý tưởng về việc có các bài kiểm tra đơn vị để kiểm tra sự phù hợp của một ứng dụng với mô hình chờ đợi vòng lặp bằng cách đánh thức nó một cách ngẫu nhiên / xác định. Hoặc nó đã có sẵn ở đâu đó?
akarnokd

Đó là một câu hỏi khác về SO: "Có một VM nghiêm ngặt nào có thể được sử dụng để thử nghiệm không?". Tôi rất thích nhìn thấy một người có bộ nhớ địa phương nghiêm ngặt - Tôi không nghĩ họ tồn tại
oxbow_lakes

8

Chỉ cần thêm điều này. Có nó xảy ra và tôi đã dành ba ngày để tìm kiếm nguyên nhân của một vấn đề đa luồng trên máy 24 lõi (JDK 6). 4 trong số 10 vụ hành quyết có kinh nghiệm mà không có bất kỳ mô hình nào. Điều này không bao giờ xảy ra trên 2 lõi hoặc 8 lõi.

Đã nghiên cứu một số tài liệu trực tuyến và đây không phải là một vấn đề Java mà là một hành vi hiếm gặp chung nhưng được mong đợi.


Xin chào ReneS, bạn (đang) phát triển ứng dụng đang chạy ở đó phải không? Liệu (đã) nó có phương thức Wait () gọi trong khi vòng lặp kiểm tra điều kiện bên ngoài như được đề xuất trong java doc docs.oracle.com/javase/6/docs/api/java/lang/ trộm ?
kẹo cao su 18/03/2015

Tôi đã viết về nó và có giải pháp là một vòng lặp while với kiểm tra điều kiện. Lỗi của tôi là vòng lặp bị thiếu ... nhưng vì vậy tôi đã tìm hiểu về những lần thức dậy này ... không bao giờ trên hai lõi, thường là trên blog 24cores.xceptance.com/2011/05/06/spquil-wakeup-the-rare-event
ReneS

Tôi đã có trải nghiệm tương tự khi tôi chạy một ứng dụng trên máy chủ unix lõi 40+. Nó đã có một số lượng cực lớn của đánh thức giả. - Vì vậy, có vẻ như số lượng đánh thức giả tỷ lệ thuận với số lượng lõi xử lý của hệ thống.
bvdb

0

https://stackoverflow.com/a/1461956/14731 chứa một lời giải thích tuyệt vời về lý do tại sao bạn cần đề phòng các cảnh báo giả ngay cả khi hệ điều hành cơ bản không kích hoạt chúng. Thật thú vị khi lưu ý rằng lời giải thích này áp dụng trên nhiều ngôn ngữ lập trình, bao gồm cả Java.


0

Trả lời câu hỏi của OP

Tôi có thể làm gì để đánh thức điều này đang chờ đợi một cách kỳ lạ mà không phải chờ đợi mãi mãi cho một sự kiện ngẫu nhiên?

, không có bất kỳ đánh thức giả nào có thể đánh thức chủ đề đang chờ này!

Cho dù wakeups giả mạo có thể hoặc không thể xảy ra trên một nền tảng cụ thể, trong một trường hợp của OP đoạn mã đó là tích cực không thể cho Condition.await()trở lại và nhìn thấy dòng "giả wakeup!" trong luồng đầu ra.

Trừ khi bạn đang sử dụng Thư viện lớp Java rất kỳ lạ

Điều này là do tiêu chuẩn, OpenJDK 's ReentrantLock' s phương pháp newCondition()trả về AbstractQueuedSynchronizer's thực hiện Conditiongiao diện, lồng nhau ConditionObject(bằng cách này, nó là việc thực hiện duy nhất của Conditiongiao diện trong thư viện lớp này), và ConditionObject' s phương pháp await()tự kiểm tra xem tình trạng này không giữ và không có bất kỳ sự thức tỉnh giả nào có thể buộc phương pháp này quay trở lại nhầm.

Nhân tiện, bạn có thể tự kiểm tra vì nó khá dễ dàng để mô phỏng đánh thức giả sau khi AbstractQueuedSynchronizerthực hiện dựa trên cơ sở. AbstractQueuedSynchronizersử dụng LockSupportcác phương thức parkunparkphương thức cấp thấp và nếu bạn gọi LockSupport.unparkmột luồng đang chờ Condition, hành động này có thể được phân biệt với một đánh thức giả.

Hơi tái cấu trúc đoạn trích của OP,

public class Spurious {

    private static class AwaitingThread extends Thread {

        @Override
        public void run() {
            Lock lock = new ReentrantLock();
            Condition cond = lock.newCondition();
            lock.lock();
            try {
                try {
                    cond.await();
                    System.out.println("Spurious wakeup!");
                } catch (InterruptedException ex) {
                    System.out.println("Just a regular interrupt.");
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private static final int AMOUNT_OF_SPURIOUS_WAKEUPS = 10;

    public static void main(String[] args) throws InterruptedException {
        Thread awaitingThread = new AwaitingThread();
        awaitingThread.start();
        Thread.sleep(10000);
        for(int i =0 ; i < AMOUNT_OF_SPURIOUS_WAKEUPS; i++)
            LockSupport.unpark(awaitingThread);
        Thread.sleep(10000);
        if (awaitingThread.isAlive())
            System.out.println("Even after " + AMOUNT_OF_SPURIOUS_WAKEUPS + " \"spurious wakeups\" the Condition is stil awaiting");
        else
            System.out.println("You are using very unusual implementation of java.util.concurrent.locks.Condition");
    }
}

và cho dù luồng xử lý (chính) khó đến mức nào sẽ cố gắng đánh thức luồng đang chờ, Condition.await()phương thức sẽ không bao giờ quay lại trong trường hợp này.

Các đánh thức giả về Conditioncác phương thức chờ đợi được thảo luận trong javadoc của Conditiongiao diện . Mặc dù nó nói rằng,

khi chờ đợi trong một Điều kiện, một sự thức tỉnh giả được phép xảy ra

và đó

khuyến cáo rằng các lập trình viên ứng dụng luôn cho rằng chúng có thể xảy ra và do đó luôn chờ trong một vòng lặp.

nhưng sau đó nói thêm rằng

Một triển khai là miễn phí để loại bỏ khả năng đánh thức giả

AbstractQueuedSynchronizerviệc triển khai Conditiongiao diện thực hiện chính xác điều đó - loại bỏ mọi khả năng đánh thức giả .

Điều này chắc chắn đúng với các ConditionObjectphương pháp chờ đợi khác.

Vì vậy, kết luận là:

chúng ta nên luôn luôn gọi Condition.awaittrong vòng lặp và kiểm tra xem điều kiện không giữ, nhưng với tiêu chuẩn, OpenJDK, Thư viện lớp Java không bao giờ có thể xảy ra . Trừ khi, một lần nữa, bạn sử dụng Thư viện lớp Java rất khác thường (phải rất khác thường, bởi vì một Thư viện lớp Java không OpenJDK nổi tiếng khác, hiện gần như đã tuyệt chủng GNU ClasspathApache Harmony , dường như giống hệt với triển khai Conditiongiao diện chuẩn)

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.