เหตุใดฟังก์ชันตัวแปรเงื่อนไขของ pthreads จึงจำเป็นต้องมี mutex


182

ฉันอ่านบนpthread.h; ฟังก์ชันเงื่อนไขที่เกี่ยวข้องกับตัวแปร (เช่นpthread_cond_wait(3)) ต้องการ mutex เป็นอาร์กิวเมนต์ ทำไม? เท่าที่ผมสามารถบอกได้ว่าผมจะได้รับการสร้าง mutex เพียงเพื่อใช้เป็นข้อโต้แย้งที่? Mutex นั้นควรทำอะไร?

คำตอบ:


194

มันเป็นเพียงวิธีการที่ปรับใช้ตัวแปรเงื่อนไข (หรือเริ่มต้น)

mutex ใช้ในการป้องกันตัวแปรสภาพตัวเอง นั่นเป็นเหตุผลที่คุณต้องล็อคมันก่อนที่จะรอ

การรอจะเป็นการปลดล็อค mutex ทำให้ผู้อื่นสามารถเข้าถึงตัวแปรเงื่อนไขได้ (สำหรับการส่งสัญญาณ) จากนั้นเมื่อตัวแปรเงื่อนไขถูกส่งสัญญาณหรือออกอากาศไปหนึ่งเธรดในรายการที่รอจะถูกปลุกและ mutex จะถูกล็อกอย่างน่าอัศจรรย์อีกครั้งสำหรับเธรดนั้น

โดยทั่วไปคุณจะเห็นการดำเนินการต่อไปนี้พร้อมกับตัวแปรเงื่อนไขซึ่งแสดงวิธีการทำงานของมัน ตัวอย่างต่อไปนี้เป็นเธรดผู้ปฏิบัติงานซึ่งให้งานผ่านสัญญาณไปยังตัวแปรเงื่อนไข

thread:
    initialise.
    lock mutex.
    while thread not told to stop working:
        wait on condvar using mutex.
        if work is available to be done:
            do the work.
    unlock mutex.
    clean up.
    exit thread.

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

รหัสด้านบนเป็นรุ่นสำหรับผู้บริโภครายเดียวเนื่องจาก Mutex ยังคงล็อคอยู่ในขณะที่กำลังทำงาน สำหรับรูปแบบที่หลากหลายผู้บริโภคคุณสามารถใช้เป็นตัวอย่าง :

thread:
    initialise.
    lock mutex.
    while thread not told to stop working:
        wait on condvar using mutex.
        if work is available to be done:
            copy work to thread local storage.
            unlock mutex.
            do the work.
            lock mutex.
    unlock mutex.
    clean up.
    exit thread.

ทำให้ผู้บริโภครายอื่นได้รับงานขณะที่ทำงานอยู่

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

lock mutex.
flag work as available.
signal condition variable.
unlock mutex.

ส่วนใหญ่ของสิ่งที่มักจะผิดพลาดที่เรียกว่าการเสแสร้งโดยทั่วไปมักจะเป็นเพราะหลายกระทู้มีสัญญาณในการpthread_cond_waitโทร (ออกอากาศ) พวกเขาจะกลับมาพร้อมกับ mutex ทำงานแล้วรออีกครั้ง

จากนั้นเธรดที่มีสัญญาณที่สองจะออกมาเมื่อไม่มีงานต้องทำ ดังนั้นคุณต้องมีตัวแปรพิเศษที่บ่งบอกว่างานควรจะทำ (นี่คือการป้องกัน mutex โดยเนื้อแท้กับคู่ condvar / mutex ที่นี่ - เธรดอื่น ๆ จำเป็นต้องล็อค mutex ก่อนที่จะเปลี่ยนอย่างไรก็ตาม)

มันเป็นไปได้ในทางเทคนิคสำหรับเธรดที่จะกลับมาจากการรอเงื่อนไขโดยไม่ถูกเตะโดยกระบวนการอื่น (นี่คือการปลุกที่ผิดจริง) แต่ในช่วงเวลาหลายปีที่ฉันทำงานกับ pthreads ทั้งในการพัฒนา / บริการของรหัสและในฐานะผู้ใช้ ของพวกเขาฉันไม่เคยได้รับหนึ่งในเหล่านี้ อาจเป็นเพราะ HP มีการใช้งานที่เหมาะสม :-)

ไม่ว่าในกรณีใดรหัสเดียวกันกับที่จัดการกับกรณีที่ผิดพลาดนั้นก็จัดการกับการปลุกแบบปลอมด้วยเช่นกันเนื่องจากจะไม่มีการตั้งค่าสถานะการทำงานที่พร้อมใช้งานให้


3
'ทำอะไรบางอย่าง' ไม่ควรอยู่ในขณะที่วนรอบ คุณต้องการให้วงวนของคุณตรวจสอบสภาพไม่เช่นนั้นคุณอาจ 'ทำอะไรบางอย่าง' ถ้าคุณตื่นขึ้นมา
เลขที่

1
ไม่การจัดการข้อผิดพลาดเป็นสองรองจากสิ่งนี้ ด้วย pthreads คุณสามารถตื่นขึ้นมาได้โดยไม่มีเหตุผลที่ชัดเจน (การปลุกแบบลวงตา) และโดยไม่มีข้อผิดพลาดใด ๆ ดังนั้นคุณต้องตรวจสอบอีกครั้ง 'เงื่อนไข' หลังจากที่คุณตื่นขึ้นมา
nos

1
ฉันไม่แน่ใจว่าฉันเข้าใจ ฉันมีปฏิกิริยาเช่นเดียวกับnos ; ทำไมdo somethingภายในwhileวง
ELLIOTTCABLE

1
บางทีฉันอาจจะไม่ชัดเจนพอ การวนซ้ำจะไม่รอให้งานพร้อมเพื่อที่คุณจะสามารถทำได้ การวนซ้ำเป็นลูปการทำงานหลัก "ไม่ จำกัด " หากคุณกลับมาจาก cond_wait และตั้งค่าสถานะงานคุณจะทำงานวนรอบอีกครั้ง "ในขณะที่เงื่อนไขบางอย่าง" จะเป็นเท็จเมื่อคุณต้องการให้เธรดหยุดทำงานที่จุดนั้นมันจะปล่อย mutex และออกได้มากที่สุด
paxdiablo

7
@stefaanv "mutex ยังคงปกป้องตัวแปรเงื่อนไขไม่มีวิธีอื่นในการปกป้อง": mutex ไม่ได้ป้องกันตัวแปรเงื่อนไข มันคือการปกป้องข้อมูลภาคแสดงแต่ฉันคิดว่าคุณรู้ว่าจากการอ่านความคิดเห็นของคุณที่ตามคำสั่งนั้น คุณสามารถส่งสัญญาณตัวแปรเงื่อนไขตามกฎหมายและสนับสนุนอย่างเต็มที่โดยการนำไปใช้งานโพสต์ -unlock ของ mutex ที่ตัดคำกริยาและในความเป็นจริงคุณจะบรรเทาความขัดแย้งในการทำเช่นนั้นในบางกรณี
WhozCraig

59

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

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

ลองพิจารณาตัวอย่างง่ายๆที่คุณได้รับแจ้งว่ามีการสร้างข้อมูลบางส่วน อาจมีเธรดอื่นสร้างข้อมูลบางส่วนที่คุณต้องการและตั้งตัวชี้ไปยังข้อมูลนั้น

จินตนาการว่าเธรดผู้ผลิตให้ข้อมูลแก่ผู้บริโภคเธรดอื่นผ่านตัวชี้ 'some_data'

while(1) {
    pthread_cond_wait(&cond); //imagine cond_wait did not have a mutex
    char *data = some_data;
    some_data = NULL;
    handle(data);
}

คุณจะได้รับสภาพการแข่งขันมากมายโดยธรรมชาติจะเกิดอะไรขึ้นถ้าอีกเธรดหนึ่งทำงานsome_data = new_dataหลังจากที่คุณตื่นขึ้นมา แต่ก่อนที่คุณจะทำdata = some_data

คุณไม่สามารถสร้าง mutex ของคุณเองเพื่อป้องกันกรณีนี้เช่น

while(1) {

    pthread_cond_wait(&cond); //imagine cond_wait did not have a mutex
    pthread_mutex_lock(&mutex);
    char *data = some_data;
    some_data = NULL;
    pthread_mutex_unlock(&mutex);
    handle(data);
}

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

ดังนั้นคุณต้องมีวิธีในการปล่อย / จับ mutex แบบอะตอมเมื่อรอ / ตื่นจากสภาพ นั่นคือสิ่งที่ตัวแปรเงื่อนไข pthread ทำและนี่คือสิ่งที่คุณต้องการ:

while(1) {
    pthread_mutex_lock(&mutex);
    while(some_data == NULL) { // predicate to acccount for spurious wakeups,would also 
                               // make it robust if there were several consumers
       pthread_cond_wait(&cond,&mutex); //atomically lock/unlock mutex
    }

    char *data = some_data;
    some_data = NULL;
    pthread_mutex_unlock(&mutex);
    handle(data);
}

(ผู้ผลิตจะต้องใช้ความระมัดระวังเหมือนกันโดยธรรมชาติเสมอปกป้อง 'some_data' ด้วย mutex เดียวกันและทำให้แน่ใจว่าจะไม่เขียนทับ some_data ถ้า some_data อยู่ในขณะนี้! = NULL)


ไม่ควรwhile (some_data != NULL)จะเป็นห่วงในขณะที่ทำเพื่อรอตัวแปรเงื่อนไขอย่างน้อยหนึ่งครั้ง?
ผู้พิพากษา Maygarden

3
ไม่สิ่งที่คุณกำลังรออยู่จริงๆคือ 'some_data' ต้องไม่ใช่ค่าว่าง หากไม่ใช่ "ครั้งแรก" ที่ยอดเยี่ยมคุณกำลังถือ mutex และสามารถใช้ข้อมูลได้อย่างปลอดภัย หากคุณมีสิ่งที่ต้องทำ / ในขณะที่คุณจะพลาดการแจ้งเตือนถ้ามีคนส่งสัญญาณตัวแปรสภาพก่อนที่คุณจะรอ (มันไม่เหมือนเหตุการณ์ที่พบใน win32 ซึ่งยังคงส่งสัญญาณจนกว่าจะมีคนรอ)
nos

4
ฉันเพิ่งสะดุดกับคำถามนี้และตรงไปตรงมาก็แปลกที่พบว่าคำตอบนี้ซึ่งถูกต้องเพียงมีคะแนนน้อยกว่าคำตอบของ paxdiablo ซึ่งมีข้อบกพร่องแน่นอน (ยังจำเป็นต้องใช้อะตอมมิก mutex สำหรับการจัดการเงื่อนไขเท่านั้น ไม่ได้สำหรับการจัดการหรือการแจ้งเตือน) ฉันเดาว่าเป็นเพียงวิธีการทำงานของ StackOverflow ...
stefaanv

@stefaanv หากคุณต้องการรายละเอียดข้อบกพร่องเป็นความคิดเห็นคำตอบของฉันดังนั้นฉันเห็นพวกเขาในเวลาที่เหมาะสมมากกว่าเดือนต่อมา :-) ฉันจะมีความสุขที่จะแก้ไขพวกเขา วลีสั้น ๆ ของคุณไม่ได้ให้รายละเอียดที่เพียงพอแก่ฉันเพื่อบอกว่าคุณกำลังพยายามพูดอะไร
paxdiablo

1
@ ไม่, ไม่ควรwhile(some_data != NULL)จะเป็นwhile(some_data == NULL)?
Eric Z

30

ตัวแปรเงื่อนไข POSIX ไม่มีสถานะ ดังนั้นจึงเป็นความรับผิดชอบของคุณในการรักษาสถานะ เนื่องจากสถานะจะสามารถเข้าถึงได้โดยเธรดทั้งสองที่รอและเธรดที่บอกเธรดอื่นให้หยุดรอดังนั้นจึงต้องมีการป้องกันโดย mutex หากคุณคิดว่าคุณสามารถใช้ตัวแปรเงื่อนไขได้โดยไม่ต้องมี mutex คุณจะไม่เข้าใจว่าตัวแปรเงื่อนไขนั้นไร้สถานะ

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

นี่คือการใช้คลาสสิกของตัวแปรเงื่อนไขง่าย:

while(1)
{
    pthread_mutex_lock(&work_mutex);

    while (work_queue_empty())       // wait for work
       pthread_cond_wait(&work_cv, &work_mutex);

    work = get_work_from_queue();    // get work

    pthread_mutex_unlock(&work_mutex);

    do_work(work);                   // do that work
}

ดูว่าเธรดกำลังรองานอยู่อย่างไร งานได้รับการคุ้มครองโดย mutex การรอจะปล่อย mutex เพื่อให้เธรดอื่นสามารถให้เธรดนี้ทำงานได้ นี่คือวิธีที่จะให้สัญญาณ:

void AssignWork(WorkItem work)
{
    pthread_mutex_lock(&work_mutex);

    add_work_to_queue(work);           // put work item on queue

    pthread_cond_signal(&work_cv);     // wake worker thread

    pthread_mutex_unlock(&work_mutex);
}

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


1
หรือเพื่อให้รัดกุมยิ่งขึ้นตัวแปรจุดทั้งหมดของเงื่อนไขคือจัดเตรียมการดำเนินการ "ปลดล็อกและรอ" ของอะตอม หากไม่มี mutex จะไม่มีสิ่งใดปลดล็อค
David Schwartz

คุณจะอธิบายความหมายของการไร้สัญชาติได้หรือไม่?
snr

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

16

ฟังก์ชันตัวแปรเงื่อนไขบางฟังก์ชันไม่จำเป็นต้องมี mutex: มีเพียงการดำเนินการที่รอคอยเท่านั้น การดำเนินการของสัญญาณและการออกอากาศไม่จำเป็นต้องมี mutex ตัวแปรเงื่อนไขยังไม่ได้เชื่อมโยงกับ mutex เฉพาะอย่างถาวร Mutex ภายนอกไม่ได้ป้องกันตัวแปรเงื่อนไข หากตัวแปรเงื่อนไขมีสถานะภายในเช่นคิวของเธรดที่รออยู่สิ่งนี้จะต้องได้รับการปกป้องโดยการล็อกภายในภายในตัวแปรเงื่อนไข

การดำเนินการรอรวบรวมตัวแปรเงื่อนไขและ mutex ไว้ด้วยกันเนื่องจาก:

  • เธรดล็อค mutex ประเมินนิพจน์บางส่วนเหนือตัวแปรที่แชร์และพบว่าเป็นเท็จเช่นนั้นต้องรอ
  • เธรดต้องย้ายจากการเป็นเจ้าของ mutex ไปยังอะตอมเพื่อรอเงื่อนไข

ด้วยเหตุผลนี้การดำเนินการรอใช้เป็นอาร์กิวเมนต์ทั้ง mutex และเงื่อนไข: เพื่อให้สามารถจัดการการถ่ายโอนอะตอมของเธรดจากการเป็นเจ้าของ mutex เพื่อรอเพื่อให้เธรดไม่ตกเป็นเหยื่อของสภาพการแข่งขันตื่นขึ้นมาที่หายไปการปลุกหายไปถึงสภาพการแข่งขัน

สภาพการแข่งขัน Wakeup ที่หายไปจะเกิดขึ้นหากเธรดเลิกใช้ mutex แล้วรอวัตถุการซิงโครไนซ์แบบไร้สถานะ แต่ในทางที่ไม่ใช่อะตอม: มีหน้าต่างเวลาเมื่อเธรดไม่มีการล็อกอีกต่อไปและมี ยังไม่ได้เริ่มรอวัตถุ ในระหว่างหน้าต่างนี้เธรดอื่นสามารถเข้ามาทำให้เงื่อนไขที่รอเป็นจริงส่งสัญญาณการซิงโครไนซ์ไร้รัฐแล้วหายไป วัตถุไร้สัญชาติจำไม่ได้ว่าเป็นสัญญาณ (ไร้สัญชาติ) ดังนั้นเธรดดั้งเดิมจะเข้าสู่โหมดสลีปออบเจ็กต์แบบไร้รัฐและไม่ตื่นขึ้นแม้ว่าเงื่อนไขที่ต้องการจะเป็นจริงแล้ว: การปลุกที่หายไป

ฟังก์ชันการรอตัวแปรเงื่อนไขหลีกเลี่ยงการปลุกที่หายไปโดยตรวจสอบให้แน่ใจว่าเธรดการโทรถูกลงทะเบียนเพื่อจับการปลุกก่อนที่จะเลิก mutex สิ่งนี้จะเป็นไปไม่ได้ถ้าฟังก์ชัน condition variable wait ไม่ได้ใช้ mutex เป็นอาร์กิวเมนต์


คุณสามารถให้ข้อมูลอ้างอิงได้หรือไม่ว่าการดำเนินการกระจายเสียงนั้นไม่จำเป็นต้องได้รับ Mutex? บน MSVC การออกอากาศจะถูกละเว้น
xvan

@xvan POSIX pthread_cond_broadcastและpthread_cond_signalการดำเนินการ (ที่คำถาม SO นี้เกี่ยวกับ) ไม่ได้ใช้ mutex เป็นอาร์กิวเมนต์ เพียงเงื่อนไข ข้อมูลจำเพาะ POSIX เป็นที่นี่ Mutex ถูกกล่าวถึงโดยเฉพาะในสิ่งที่เกิดขึ้นในเธรดที่รอเมื่อพวกเขาตื่นขึ้นมา
Kaz

คุณจะอธิบายความหมายของการไร้สัญชาติได้หรือไม่?
snr

1
@snr วัตถุการซิงโครไนซ์ไร้รัฐไม่จดจำสถานะใด ๆ ที่เกี่ยวข้องกับการส่งสัญญาณ เมื่อมีการส่งสัญญาณหากมีสิ่งใดรออยู่ในตอนนี้มันจะถูกปลุกขึ้นมิฉะนั้นการปลุกจะถูกลืม ตัวแปรเงื่อนไขไร้สัญชาติเช่นนี้ สถานะที่จำเป็นในการทำให้การซิงโครไนซ์มีความน่าเชื่อถือได้รับการดูแลโดยแอปพลิเคชันและได้รับการป้องกันโดย mutex ที่ใช้ร่วมกับตัวแปรเงื่อนไขตามตรรกะที่เขียนอย่างถูกต้อง
Kaz

7

ฉันไม่พบคำตอบอื่นให้กระชับและอ่านง่ายเหมือนหน้านี้ โดยปกติโค้ดที่รอจะมีหน้าตาดังนี้:

mutex.lock()
while(!check())
    condition.wait()
mutex.unlock()

มีสามเหตุผลในการตัดคำwait()ใน mutex:

  1. โดยไม่มี mutex อีกเธรดสามารถทำได้signal()ก่อนwait()และเราจะพลาดการปลุกนี้
  2. ปกติ check()จะขึ้นอยู่กับการดัดแปลงจากเธรดอื่นดังนั้นคุณต้องแยกออกจากกัน
  3. เพื่อให้แน่ใจว่าเธรดลำดับความสำคัญสูงสุดดำเนินการก่อน (คิวสำหรับ mutex อนุญาตให้ตัวกำหนดตารางเวลาตัดสินใจว่าใครจะไปต่อไป)

ประเด็นที่สามไม่ได้เป็นปัญหาเสมอไป - บริบททางประวัติศาสตร์เชื่อมโยงจากบทความถึง การสนทนานี้

การแจ้งเตือนแบบปลอมมักถูกกล่าวถึงเกี่ยวกับกลไกนี้ (เช่นเธรดที่รออยู่นั้นถูกปลุกโดยไม่signal()ถูกเรียก) check()อย่างไรก็ตามเหตุการณ์ดังกล่าวจะถูกจัดการโดยคล้อง


4

ตัวแปรเงื่อนไขเกี่ยวข้องกับ mutex เพราะเป็นวิธีเดียวที่สามารถหลีกเลี่ยงการแข่งขันที่ถูกออกแบบมาเพื่อหลีกเลี่ยง

// incorrect usage:
// thread 1:
while (notDone) {
    pthread_mutex_lock(&mutex);
    bool ready = protectedReadyToRunVariable
    pthread_mutex_unlock(&mutex);
    if (ready) {
        doWork();
    } else {
        pthread_cond_wait(&cond1); // invalid syntax: this SHOULD have a mutex
    }
}

// signalling thread
// thread 2:
prepareToRunThread1();
pthread_mutex_lock(&mutex);
   protectedReadyToRuNVariable = true;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond1);

Now, lets look at a particularly nasty interleaving of these operations

pthread_mutex_lock(&mutex);
bool ready = protectedReadyToRunVariable;
pthread_mutex_unlock(&mutex);
                                 pthread_mutex_lock(&mutex);
                                 protectedReadyToRuNVariable = true;
                                 pthread_mutex_unlock(&mutex);
                                 pthread_cond_signal(&cond1);
if (ready) {
pthread_cond_wait(&cond1); // uh o!

ณ จุดนี้ไม่มีเธรดที่จะส่งสัญญาณตัวแปรเงื่อนไขดังนั้น thread1 จะรอตลอดไปแม้ว่าการป้องกัน READToRunVariable ที่มีการป้องกันจะกล่าวว่าพร้อมที่จะไป!

วิธีการเดียวสำหรับสิ่งนี้คือตัวแปรเงื่อนไขเพื่อปล่อย mutex แบบอะตอมในขณะที่เริ่มต้นพร้อมที่จะรอตัวแปรเงื่อนไข นี่คือสาเหตุที่ฟังก์ชัน cond_wait ต้องการ mutex

// correct usage:
// thread 1:
while (notDone) {
    pthread_mutex_lock(&mutex);
    bool ready = protectedReadyToRunVariable
    if (ready) {
        pthread_mutex_unlock(&mutex);
        doWork();
    } else {
        pthread_cond_wait(&mutex, &cond1);
    }
}

// signalling thread
// thread 2:
prepareToRunThread1();
pthread_mutex_lock(&mutex);
   protectedReadyToRuNVariable = true;
   pthread_cond_signal(&mutex, &cond1);
pthread_mutex_unlock(&mutex);

3

mutex ควรถูกล็อคเมื่อคุณโทร pthread_cond_wait ; เมื่อคุณเรียกมันว่าทั้งสองจะปลดล็อค mutex และบล็อกเงื่อนไข เมื่อเงื่อนไขถูกส่งสัญญาณมันจะล็อคอะตอมอีกครั้งและส่งกลับ

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


ดังนั้น…มีเหตุผลไหมที่ฉันจะไม่ปล่อยให้ mutex ปลดล็อกตลอดเวลาจากนั้นล็อคมันไว้ก่อนรอแล้วปลดล็อคทันทีหลังจากรอเสร็จหรือไม่
ELLIOTTCABLE

Mutex ยังแก้ปัญหาการแข่งขันที่อาจเกิดขึ้นระหว่างเธรดที่รอและส่งสัญญาณ ตราบใดที่ mutex ถูกล็อกอยู่เสมอเมื่อเปลี่ยนเงื่อนไขและการส่งสัญญาณคุณจะไม่มีวันพบว่าคุณพลาดสัญญาณและหลับตลอดไป
Hasturkun

ดังนั้น ... ฉันควรจะเป็นครั้งแรกรอ-on-mutex บน mutex conditionvar ฯ ก่อนรอ conditionvar หรือไม่ ฉันไม่แน่ใจว่าฉันเข้าใจเลย
ELLIOTTCABLE

2
@elliottcable: โดยไม่ต้องถือ mutex คุณจะรู้ได้อย่างไรว่าคุณควรหรือไม่ควรรอ เกิดอะไรขึ้นถ้าสิ่งที่คุณกำลังรอเพียงที่เกิดขึ้น?
David Schwartz

1

ฉันทำแบบฝึกหัดในชั้นเรียนถ้าคุณต้องการตัวอย่างจริงของตัวแปรเงื่อนไข:

#include "stdio.h"
#include "stdlib.h"
#include "pthread.h"
#include "unistd.h"

int compteur = 0;
pthread_cond_t varCond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex_compteur;

void attenteSeuil(arg)
{
    pthread_mutex_lock(&mutex_compteur);
        while(compteur < 10)
        {
            printf("Compteur : %d<10 so i am waiting...\n", compteur);
            pthread_cond_wait(&varCond, &mutex_compteur);
        }
        printf("I waited nicely and now the compteur = %d\n", compteur);
    pthread_mutex_unlock(&mutex_compteur);
    pthread_exit(NULL);
}

void incrementCompteur(arg)
{
    while(1)
    {
        pthread_mutex_lock(&mutex_compteur);

            if(compteur == 10)
            {
                printf("Compteur = 10\n");
                pthread_cond_signal(&varCond);
                pthread_mutex_unlock(&mutex_compteur);
                pthread_exit(NULL);
            }
            else
            {
                printf("Compteur ++\n");
                compteur++;
            }

        pthread_mutex_unlock(&mutex_compteur);
    }
}

int main(int argc, char const *argv[])
{
    int i;
    pthread_t threads[2];

    pthread_mutex_init(&mutex_compteur, NULL);

    pthread_create(&threads[0], NULL, incrementCompteur, NULL);
    pthread_create(&threads[1], NULL, attenteSeuil, NULL);

    pthread_exit(NULL);
}

1

มันเป็นการตัดสินใจออกแบบที่เฉพาะเจาะจงมากกว่าความต้องการทางความคิด

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

https://linux.die.net/man/3/pthread_cond_wait

คุณสมบัติของ Mutexes และตัวแปรเงื่อนไข

มีข้อเสนอแนะว่าการได้มาและการเปิดเผย mutex จะถูกแยกออกจากการรอเงื่อนไข สิ่งนี้ถูกปฏิเสธเนื่องจากเป็นลักษณะที่รวมกันของการดำเนินการซึ่งในความเป็นจริงแล้วจะช่วยให้เกิดการใช้งานแบบเรียลไทม์ การประยุกต์ใช้เหล่านั้นสามารถย้ายเธรดที่มีลำดับความสำคัญสูงระหว่างตัวแปรเงื่อนไขและ mutex ในลักษณะที่โปร่งใสให้กับผู้เรียก สิ่งนี้สามารถป้องกันการสลับบริบทเพิ่มเติมและจัดเตรียมการได้มาซึ่ง mutex ที่กำหนดได้มากขึ้นเมื่อเธรดที่รออยู่ถูกส่งสัญญาณ ดังนั้นปัญหาความเป็นธรรมและลำดับความสำคัญสามารถจัดการได้โดยตรงโดยการกำหนดเวลา นอกจากนี้การดำเนินการรอสภาพปัจจุบันตรงกับแนวปฏิบัติที่มีอยู่


0

มีผู้ exegeses มากมายเกี่ยวกับเรื่องนั้น แต่ฉันต้องการที่จะเป็นตัวอย่างที่ชัดเจนด้วยตัวอย่างต่อไปนี้

1 void thr_child() {
2    done = 1;
3    pthread_cond_signal(&c);
4 }

5 void thr_parent() {
6    if (done == 0)
7        pthread_cond_wait(&c);
8 }

เกิดอะไรขึ้นกับข้อมูลโค้ด แค่ไตร่ตรองบ้างก่อนจะไปข้างหน้า


ปัญหาเป็นเรื่องที่ลึกซึ้งอย่างแท้จริง หากผู้ปกครองเรียกใช้ thr_parent()แล้ว vets มูลค่าของdoneมันจะเห็นว่ามันเป็น0และพยายามที่จะไปนอน แต่ก่อนที่จะมีสายรอให้เข้านอนพ่อแม่จะถูกขัดจังหวะระหว่างบรรทัด 6-7 และลูกวิ่ง เด็กเปลี่ยนตัวแปรสถานะ doneเป็น1และสัญญาณ แต่ไม่มีเธรดรออยู่และไม่มีเธรดถูกปลุก เมื่อผู้ปกครองทำงานอีกครั้งมันจะนอนหลับตลอดไปซึ่งไม่น่าเชื่อจริงๆ

เกิดอะไรขึ้นถ้าพวกเขาจะดำเนินการในขณะที่ได้รับล็อคเป็นรายบุคคล?

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