การปลุกกลอุบายใน Java เกิดขึ้นจริงหรือไม่?


208

เห็นคำถามที่เกี่ยวข้องกับการล็อคต่าง ๆ และ (เกือบ) มักจะหาคำว่า 'ห่วงเพราะของการปลุกซ้ำ' 1ฉันสงสัยว่ามีใครเคยมีประสบการณ์การปลุกดังกล่าวบ้าง (สมมติว่าเป็นตัวอย่างของฮาร์ดแวร์ / ซอฟต์แวร์ที่เหมาะสม)

ฉันรู้ว่าคำว่า 'ปลอม' หมายถึงไม่มีเหตุผลที่ชัดเจน แต่อะไรคือสาเหตุของเหตุการณ์ดังกล่าว

( 1หมายเหตุ: ผมไม่ได้ตั้งคำถามการปฏิบัติวนลูป.)

แก้ไข:คำถามที่ผู้ช่วย (สำหรับผู้ที่ชอบตัวอย่างโค้ด):

หากฉันมีโปรแกรมต่อไปนี้และฉันเรียกใช้:

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();
        }
    }
}

ฉันจะทำอย่างไรเพื่อปลุกสิ่งนี้อย่างawaitไม่เกรงกลัวโดยไม่รอให้เหตุการณ์สุ่ม


1
สำหรับ JVMs ที่ทำงานบนระบบ POSIX และใช้pthread_cond_wait()คำถามจริงคือ"ทำไม pthread_cond_wait ถึงมีการปลุกที่ผิดพลาด" .
Flow

คำตอบ:


204

บทความวิกิพีเดียเกี่ยวกับการปลอมแปลงมีการขโมยอาหารอันโอชะนี้

มีการpthread_cond_wait()ใช้งานฟังก์ชันใน Linux โดยใช้การfutexเรียกของระบบ การเรียกระบบการบล็อกแต่ละครั้งบน Linux จะส่งคืนทันทีEINTRเมื่อกระบวนการได้รับสัญญาณ ... pthread_cond_wait()ไม่สามารถรีสตาร์ทการรอได้เนื่องจากอาจพลาดการปลุกที่แท้จริงในเวลาเพียงเล็กน้อยที่อยู่นอกการfutexเรียกของระบบ สภาพการแข่งขันนี้สามารถหลีกเลี่ยงได้โดยการตรวจสอบผู้โทรหาค่าคงที่ สัญญาณ POSIX จะสร้างสัญญาณเตือนปลอม

สรุป : หากกระบวนการลอจิคัลส่งสัญญาณว่าเธรดการรอแต่ละโพรเซสจะเพลิดเพลินกับการปลุกที่ดี

ฉันซื้อมัน. นั่นเป็นยาเม็ดที่กลืนง่ายกว่าเหตุผล "สำหรับการแสดง" ที่มักคลุมเครือ


13
คำอธิบายที่ดีกว่าที่นี่: stackoverflow.com/questions/1461913/…
Gili

3
การเลิกบล็อก EINTR นี้เป็นจริงของการเรียกระบบการบล็อกทั้งหมดในระบบที่ได้รับ Unix สิ่งนี้ทำให้เคอร์เนลง่ายขึ้นมาก แต่โปรแกรมเมอร์แอปพลิเคชันซื้อภาระ
Tim Williscroft

2
ฉันคิดว่า pthread_cond_wait () และเพื่อนไม่สามารถคืน EINTR ได้ แต่ส่งคืนศูนย์ถ้าตื่นขึ้นมาอย่างไม่เกรงใจ? จาก: pubs.opengroup.org/onlinepubs/7908799/xsh/… "ฟังก์ชั่นเหล่านี้จะไม่ส่งคืนรหัสข้อผิดพลาดของ [EINTR]"
gubby

2
@jgubby ใช่แล้ว การfutex()โทรพื้นฐานส่งกลับEINTRแต่ค่าส่งคืนนั้นไม่ได้รับการอัปเดตเป็นระดับถัดไป ผู้เรียก pthread ต้องตรวจสอบค่าคงที่ สิ่งที่พวกเขากำลังพูดคือเมื่อpthread_cond_wait()กลับมาคุณจะต้องตรวจสอบสภาพลูปของคุณ (ค่าคงที่) อีกครั้งเพราะการรออาจจะถูกปลุกขึ้นมาอย่างไม่เกรงกลัว การรับสัญญาณระหว่างการโทรของระบบเป็นสาเหตุหนึ่งที่เป็นไปได้ แต่ไม่ใช่เพียงสัญญาณเดียว
John Kugelman

1
สันนิษฐานได้ว่าpthreadห้องสมุดสามารถให้ค่าคงที่ของตัวเองและตรรกะการตรวจสอบของตัวเองเพื่อที่จะกำจัดการปลุกปลอมโดยไม่ต้องรับผิดชอบต่อผู้ใช้ ที่จะ (ผลกระทบต่อประสิทธิภาพการทำงานที่อ้าง)

22

ฉันมีระบบการผลิตที่แสดงพฤติกรรมนี้ เธรดรอสัญญาณว่ามีข้อความอยู่ในคิว ในช่วงเวลาที่ยุ่งมากถึง 20% ของการปลุกให้ตื่นขึ้น (เช่นเมื่อมันตื่นขึ้นมาจะไม่มีอะไรอยู่ในคิว) เธรดนี้เป็นผู้บริโภคข้อความเท่านั้น มันทำงานบนกล่องโปรเซสเซอร์ 8 SLES-10 Linux และสร้างขึ้นด้วย GCC 4.1.2 ข้อความมาจากแหล่งภายนอกและประมวลผลแบบอะซิงโครนัสเนื่องจากมีปัญหาหากระบบของฉันอ่านไม่เร็วพอ


15

เพื่อตอบคำถามในการไต่สวน - ใช่! มันเกิดขึ้นได้บทความวิกิกล่าวถึงข้อตกลงที่ดีเกี่ยวกับการปลอมตัวปลอมคำอธิบายที่ดีสำหรับสิ่งเดียวกันกับที่ฉันได้พบมีดังนี้ -

ลองคิดดูสิ ... เช่นเดียวกับรหัสใด ๆ ตัวจัดตารางเวลาเธรดอาจปิดทึบชั่วคราวเนื่องจากมีสิ่งผิดปกติเกิดขึ้นในฮาร์ดแวร์ / ซอฟต์แวร์พื้นฐาน แน่นอนควรใช้ความระมัดระวังเพื่อให้สิ่งนี้เกิดขึ้นน้อยที่สุดเท่าที่จะเป็นไปได้ แต่เนื่องจากไม่มีซอฟต์แวร์ที่แข็งแกร่ง 100% จึงมีเหตุผลที่จะถือว่าสิ่งนี้สามารถเกิดขึ้นได้และดูแลการกู้คืนที่สง่างามในกรณีที่ โดยสังเกตการเต้นของหัวใจที่หายไป)

ตอนนี้ตัวจัดกำหนดการสามารถกู้คืนได้อย่างไรโดยคำนึงว่าในระหว่างที่มีการปิดบังข้อมูลอาจมีสัญญาณบางอย่างที่ตั้งใจแจ้งให้ทราบถึงการรอเธรด หากตัวกำหนดตารางเวลาไม่ทำอะไรเลยหัวข้อที่ "โชคร้าย" ที่กล่าวถึงจะแขวนรอตลอดไป - เพื่อหลีกเลี่ยงปัญหานี้ตัวกำหนดตารางเวลาจะส่งสัญญาณไปยังเธรดที่รออยู่ทั้งหมด

สิ่งนี้ทำให้จำเป็นในการสร้าง "สัญญา" ที่รอเธรดสามารถแจ้งเตือนได้โดยไม่มีเหตุผล เพื่อความแม่นยำจะมีเหตุผล - ตัวกำหนดเวลาปิดทึบ - แต่เนื่องจากเธรดได้รับการออกแบบ (เพื่อเหตุผลที่ดี) เพื่อลบเลือนรายละเอียดการใช้งานตัวกำหนดเวลาภายในเหตุผลนี้น่าจะดีกว่าที่จะแสดงเป็น "ปลอม"

ฉันอ่านคำตอบนี้จากแหล่งที่มาและพบว่ามันสมเหตุสมผลพอ ยังอ่าน

wakeups ปลอมในชวาและวิธีการหลีกเลี่ยงพวกเขา

PS: ลิงค์ด้านบนคือบล็อกส่วนตัวของฉันที่มีรายละเอียดเพิ่มเติมเกี่ยวกับการปลุกซ้ำ ๆ


9

Cameron Purdyเขียนบล็อกโพสต์ในขณะที่กลับมาเกี่ยวกับการถูกตีด้วยปัญหาการปลุกปลอม ใช่แล้วมันเกิดขึ้น

ฉันคาดเดาว่ามันอยู่ในข้อมูลจำเพาะ (เป็นไปได้) เนื่องจากข้อ จำกัด ของบางแพลตฟอร์มที่ใช้งาน Java? แม้ว่าฉันอาจจะผิด!


ฉันอ่านโพสต์และทำให้ฉันมีความคิดเกี่ยวกับการทดสอบหน่วยเพื่อทดสอบความสอดคล้องของแอปพลิเคชันหนึ่งกับกระบวนทัศน์ลูป - รอโดยการปลุกขึ้นแบบสุ่ม / กำหนดล่วงหน้า หรือมันมีอยู่แล้วที่ไหนสักแห่ง?
akarnokd

เป็นอีกคำถามหนึ่งใน SO: "มีVM ที่เข้มงวดซึ่งสามารถใช้สำหรับการทดสอบได้หรือไม่" ฉันชอบที่จะเห็นคนที่มีหน่วยความจำด้าย - เข้มงวด - ฉันยังไม่คิดว่าพวกเขามีอยู่จริง
oxbow_lakes

8

เพียงเพิ่ม ใช่มันเกิดขึ้นและฉันใช้เวลาสามวันในการค้นหาสาเหตุของปัญหามัลติเธรดในเครื่อง 24 คอร์ (JDK 6) 4 จาก 10 การประหารชีวิตพบว่าไม่มีรูปแบบใด ๆ สิ่งนี้ไม่เคยเกิดขึ้นกับ 2 คอร์หรือ 8 คอร์

ศึกษาเนื้อหาออนไลน์และนี่ไม่ใช่ปัญหาของจาวา แต่เป็นพฤติกรรมที่หายากโดยทั่วไป แต่คาดว่าจะเกิดขึ้น


สวัสดี ReneS คุณพัฒนาแอปที่รันอยู่หรือเปล่า (ทำ) มันมีการรอ () วิธีการโทรในขณะที่ตรวจสอบสภาพภายนอกตามที่แนะนำใน java doc docs.oracle.com/javase/6/docs/api/java/lang/…หรือไม่?
gumkins

ฉันเขียนเกี่ยวกับมันและใช่วิธีแก้ปัญหาคือในขณะที่วนกับการตรวจสอบสภาพ ความผิดพลาดของฉันคือวงที่หายไป ... แต่ดังนั้นฉันจึงได้เรียนรู้เกี่ยวกับการปลุกเหล่านี้ ... ไม่เคยอยู่ในสองคอร์มักจะอยู่ที่ 24cores blog.xceptance.com/2011/05/06/spurious-wakeup-the-rare-event
ReneS

ฉันมีประสบการณ์ที่คล้ายกันเมื่อฉันรันแอปพลิเคชันบนเซิร์ฟเวอร์ 40 + core unix มันมีการปลุกที่ผิดมาก ๆ - ดังนั้นดูเหมือนว่าปริมาณของการปลุกแบบลวงตาจะแปรผันตรงกับปริมาณของตัวประมวลผลหลักของระบบ
bvdb

0

https://stackoverflow.com/a/1461956/14731มีคำอธิบายที่ยอดเยี่ยมว่าทำไมคุณต้องป้องกันการตื่นขึ้นมาโดยไม่เชื่อแม้ว่าระบบปฏิบัติการที่อยู่ด้านล่างจะไม่ทริกเกอร์ก็ตาม เป็นที่น่าสนใจที่จะทราบว่าคำอธิบายนี้ใช้กับภาษาการเขียนโปรแกรมหลายภาษารวมถึง Java


0

ตอบคำถามของ OP

ฉันจะทำอย่างไรเพื่อปลุกสิ่งนี้รออย่างไม่หยุดยั้งโดยไม่ต้องรอตลอดไปสำหรับเหตุการณ์สุ่ม

, ไม่มีใด ๆ ปลุกปลอมอาจจะตื่นขึ้นมานี้ด้ายรอ!

โดยไม่คำนึงว่า wakeups ปลอมสามารถหรือไม่สามารถเกิดขึ้นได้บนแพลตฟอร์มโดยเฉพาะอย่างยิ่งในกรณีของ OP ที่ snippet มันเป็นบวกเป็นไปไม่ได้สำหรับการCondition.await()จะกลับมาและจะเห็นเส้น "ปลุกเก๊!" ในเอาต์พุตสตรีม

นอกจากว่าคุณกำลังใช้Java Class Library ที่แปลกใหม่มาก

เพราะนี่คือมาตรฐานOpenJDK 's ReentrantLock' s วิธีการnewCondition()ผลตอบแทนAbstractQueuedSynchronizerของการดำเนินงานของConditionอินเตอร์เฟซที่ซ้อนกันConditionObject(โดยวิธีการก็คือการดำเนินการเฉพาะของConditionอินเตอร์เฟซในห้องสมุดชั้นนี้) และConditionObject's วิธีการawait()ของตัวเองการตรวจสอบว่าอยู่ในสภาพที่ไม่ได้ การถือครองและไม่มีการปลอมแปลงใด ๆ ที่ทำให้การปลุกผิดพลาดสามารถบังคับให้วิธีนี้กลับมาผิดพลาดได้

โดยวิธีการที่คุณสามารถตรวจสอบด้วยตัวคุณเองว่ามันเป็นเรื่องง่ายที่จะเลียนแบบการปลอมเมื่อตื่นนอนการAbstractQueuedSynchronizerดำเนินการตามฐานที่เกี่ยวข้อง AbstractQueuedSynchronizerใช้ในระดับต่ำLockSupport's parkและunparkวิธีการและถ้าคุณเรียกLockSupport.unparkบนด้ายรอในConditionการดำเนินการนี้ไม่สามารถแยกจากปลุกปลอม

ปรับโครงสร้างเล็กน้อยของ 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");
    }
}

และไม่ว่าเธรด (หลัก) ที่ไม่มีการดึงข้อมูลจะพยายามปลุกเธรดที่กำลังรออยู่มากเพียงใดCondition.await()เมธอดจะไม่ส่งคืนในกรณีนี้

wakeups ปลอมบนCondition's วิธีการรอที่จะกล่าวถึงในJavadoc ของConditionอินเตอร์เฟซ แม้ว่ามันจะบอกว่า

เมื่อรอเงื่อนไขจะอนุญาตให้มีการปลุกปลอมโดยเกิดขึ้น

และนั่น

ขอแนะนำให้โปรแกรมเมอร์แอปพลิเคชันคิดเสมอว่าสามารถเกิดขึ้นได้และรอเป็นวงอยู่เสมอ

แต่ต่อมามันก็เสริมว่า

การใช้งานมีอิสระที่จะลบความเป็นไปได้ของการปลุกปลอม

และAbstractQueuedSynchronizerของการดำเนินงานของConditionอินเตอร์เฟซที่ไม่ตรงที่ - เอาไปได้ของการ wakeups

สิ่งนี้ถือเป็นจริงสำหรับConditionObjectวิธีการรอของผู้อื่น

ดังนั้นข้อสรุปคือ:

เราควรโทรCondition.awaitเข้าไปในลูปเสมอและตรวจสอบว่าเงื่อนไขไม่ได้เก็บไว้ แต่ด้วยมาตรฐาน OpenJDK, Java Class Library จะไม่มีทางเกิดขึ้นได้ นอกจากนั้นคุณใช้ Java Class Library ที่ผิดปกติมาก (ซึ่งจะต้องผิดปกติมากเพราะไลบรารี Java Class อื่นที่ไม่ใช่ OpenJDK Java ที่รู้จักกันดีในปัจจุบันเกือบจะสูญพันธุ์GNU ClasspathและApache Harmonyดูเหมือนจะเหมือนกับการใช้มาตรฐานของConditionอินเตอร์เฟส)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.