เกิดอะไรขึ้นกับเธรดที่แยกออกเมื่อ main () ออกมา


153

สมมติว่าฉันเริ่มต้นstd::threadและจากนั้นdetach()เธรดจึงยังคงดำเนินการต่อแม้ว่าสิ่งstd::threadนั้นจะเป็นตัวแทนของมันก็ตาม

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

ฉันไม่พบสิ่งใดในมาตรฐาน (แม่นยำกว่าในร่าง N3797 C ++ 14) ซึ่งอธิบายสิ่งที่ควรเกิดขึ้นทั้ง 1.10 และ 30.3 ไม่มีถ้อยคำที่เกี่ยวข้อง

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

ถ้าวิ่งออกมาจากmain()กับหัวข้อแฝดทำงานเป็นพฤติกรรมที่ไม่ได้กำหนดไว้แล้วใด ๆ ที่ใช้std::thread::detach()เป็นพฤติกรรมที่ไม่ได้กำหนดเว้นแต่หัวข้อหลักไม่เคยออกจาก2

ดังนั้นการรันmain()เธรดที่แยกออกมาจะต้องมีเอฟเฟกต์ที่กำหนดไว้ คำถามคือ: ที่ไหน (ในมาตรฐาน C ++ , ไม่ใช่ POSIX, ไม่ใช่ OS เอกสาร, ... ) เป็นเอฟเฟกต์เหล่านั้นที่กำหนดไว้

2ไม่สามารถเข้าร่วมเธรดเดี่ยวได้ (ในแง่ของstd::thread::join()) คุณสามารถรอผลจากหัวข้อแฝด (เช่นผ่านทางในอนาคตจากstd::packaged_taskหรือโดยสัญญาณการนับหรือธงและตัวแปรสภาพ) แต่ที่ไม่ได้รับประกันว่าด้ายได้เสร็จสิ้นการดำเนินการ อันที่จริงถ้าคุณใส่ส่วนการส่งสัญญาณเข้าไปในเตาเผาของวัตถุอัตโนมัติครั้งแรกของด้ายที่มีจะโดยทั่วไปจะเป็นรหัส (destructors) ที่ระยะหลังรหัสการส่งสัญญาณ หากระบบปฏิบัติการจัดตารางเวลาเธรดหลักเพื่อใช้ผลลัพธ์และออกก่อนที่เธรดที่แยกออกจะเสร็จสิ้นการรัน destructors ที่กล่าวไว้สิ่งใดที่ ^ Wis จะนิยามไว้จะเกิดอะไรขึ้น


5
ฉันสามารถค้นหาโน้ตที่ไม่บังคับใช้ที่คลุมเครือใน [basic.start.term] / 4: "การยกเลิกเธรดทุกครั้งก่อนที่จะโทรstd::exitหรือออกจากmainนั้นเพียงพอ แต่ไม่จำเป็นเพื่อตอบสนองความต้องการเหล่านี้" (ย่อหน้าทั้งหมดอาจมีความเกี่ยวข้อง) โปรดดู [support.start.term] / 8 ( std::exitเรียกว่าเมื่อmainส่งคืน)
dyp

คำตอบ:


45

คำตอบสำหรับคำถามเดิม "เกิดอะไรขึ้นกับเธรดเดี่ยวเมื่อmain()ออก" คือ:

มันทำงานต่อไป (เพราะมาตรฐานไม่ได้บอกว่ามันหยุด) และมันถูกกำหนดไว้อย่างดีตราบใดที่มันไม่ได้สัมผัสตัวแปร (thread | automatic_local) ของเธรดอื่นหรือวัตถุคงที่

สิ่งนี้ดูเหมือนว่าได้รับอนุญาตให้จัดการเธรดเป็นวัตถุแบบสแตติก (หมายเหตุใน[basic.start.term] / 4พูดได้มากขอบคุณ @dyp สำหรับตัวชี้)

ปัญหาเกิดขึ้นเมื่อการทำลายวัตถุคงที่เสร็จสิ้นแล้วเพราะการดำเนินการเข้าสู่ระบอบการปกครองที่รหัสเท่านั้นที่ได้รับอนุญาตในตัวจัดการสัญญาณอาจดำเนินการ ( [basic.start.term] / 1 ประโยคที่ 1 ) ของไลบรารีมาตรฐาน C ++ นั่นเป็นเพียง<atomic>ไลบรารี ( [support.runtime] / 9, ประโยคที่ 2 ) โดยเฉพาะอย่างยิ่งนั้นโดยทั่วไปแล้วจะแยกออกจากกัน condition_variable (เป็นตัวกำหนดการใช้งานว่าจะบันทึกไว้เพื่อใช้ในตัวจัดการสัญญาณหรือไม่เพราะไม่ใช่ส่วนหนึ่งของ<atomic>)

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

คำตอบของคำถามที่สอง "สามารถแยกเธรดที่เคยเข้าร่วมได้อีก" คือ:

ใช่กับ*_at_thread_exitครอบครัวของฟังก์ชั่น ( notify_all_at_thread_exit(), std::promise::set_value_at_thread_exit(), ... )

ตามที่ระบุไว้ในเชิงอรรถ [2] ของคำถามสัญญาณตัวแปรเงื่อนไขหรือสัญญาณหรือเคาน์เตอร์อะตอมไม่เพียงพอที่จะเข้าร่วมด้ายเดี่ยว (ในความรู้สึกของการสร้างความมั่นใจว่าจุดสิ้นสุดของการดำเนินการที่ได้-ที่เกิดขึ้นก่อนที่จะได้รับของ กล่าวว่าการส่งสัญญาณโดยเธรดที่รอ) โดยทั่วไปจะมีการประมวลผลโค้ดเพิ่มเติมหลังจากเช่นnotify_all()ตัวแปรสภาพโดยเฉพาะ destructors ของวัตถุอัตโนมัติและเธรดโลคัล

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

ดังนั้นเพื่อหลีกเลี่ยงพฤติกรรมที่ไม่ได้กำหนดในกรณีที่ไม่มีการใช้งานใด ๆ รับประกันเหนือสิ่งที่ต้องการมาตรฐานคุณต้อง (ด้วยตนเอง) เข้าร่วมเธรดเดี่ยวกับ_at_thread_exitฟังก์ชั่นที่ทำการส่งสัญญาณหรือทำให้เธรดเดี่ยวรันโค้ดเท่านั้นที่ปลอดภัยสำหรับ ตัวจัดการสัญญาณเช่นกัน


17
คุณแน่ใจเกี่ยวกับเรื่องนี้? ทุกที่ที่ฉันทดสอบ (GCC 5, clang 3.5, MSVC 14), เธรดแยกเดี่ยวทั้งหมดจะถูกฆ่าเมื่อออกจากเธรดหลัก
rustyx

3
ฉันเชื่อว่าปัญหาไม่ใช่สิ่งที่การใช้งานเฉพาะเจาะจงทำ แต่เป็นวิธีการหลีกเลี่ยงสิ่งที่มาตรฐานกำหนดว่าเป็นพฤติกรรมที่ไม่ได้กำหนด
Jon Spencer

7
คำตอบนี้ดูเหมือนจะบอกเป็นนัยว่าหลังจากการทำลายตัวแปรสแตติกกระบวนการจะเข้าสู่สถานะสลีปบางประเภทเพื่อรอเธรดที่เหลือให้เสร็จสิ้น นั่นไม่เป็นความจริงหลังจากexitเสร็จสิ้นการทำลายวัตถุสแตติกรันatexitตัวจัดการฟลัชสตรีม ฯลฯ มันจะส่งคืนการควบคุมสภาพแวดล้อมของโฮสต์เช่นกระบวนการออกจาก หากเธรดที่แยกออกมายังคงทำงานอยู่ (และหลีกเลี่ยงพฤติกรรมที่ไม่ได้กำหนดโดยไม่แตะสิ่งใดนอกเธรดของตนเอง) ก็จะหายไปในกลุ่มของควันเมื่อกระบวนการออกไป
โจนาธาน Wakely

3
หากคุณตกลงโดยใช้ ISO ที่ไม่ใช่ ISO C ++ API หากการmainโทรpthread_exitแทนการส่งคืนหรือการโทรexitซึ่งจะทำให้กระบวนการรอเธรดเดี่ยวที่จะเสร็จสิ้นจากนั้นโทรexitหลังจากการโทรสุดท้ายเสร็จสิ้น
โจนาธาน Wakely

3
"มันยังคงทำงานอยู่ (เพราะมาตรฐานไม่ได้บอกว่ามันหยุด)" -> ทุกคนสามารถบอกฉันได้อย่างไรเธรดสามารถดำเนินการต่อเนื่องระหว่างกระบวนการคอนเทนเนอร์ได้อย่างไร
Gupta

42

กำลังถอดเธรด

ตามstd::thread::detach:

แยกเธรดของการดำเนินการจากวัตถุเธรดอนุญาตให้ดำเนินการต่อเพื่อดำเนินการต่ออย่างอิสระ รีซอร์สที่จัดสรรใด ๆ จะถูกทำให้ว่างเมื่อเธรดออก

จากpthread_detach:

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

การแยกเธรดเป็นส่วนใหญ่สำหรับการบันทึกทรัพยากรในกรณีที่แอปพลิเคชันไม่จำเป็นต้องรอให้เธรดเสร็จสิ้น (เช่น daemons ซึ่งต้องรันจนกว่าการยกเลิกกระบวนการ):

  1. เมื่อต้องการปล่อยให้หมายเลขอ้างอิงด้านแอปพลิเคชัน: หนึ่งสามารถปล่อยให้std::threadวัตถุออกจากขอบเขตโดยไม่ต้องเข้าร่วมโดยปกติสิ่งที่นำไปสู่การเรียกร้องให้std::terminate()ทำลาย
  2. เพื่อให้ระบบปฏิบัติการล้างข้อมูลทรัพยากรเฉพาะเธรด ( TCB ) โดยอัตโนมัติทันทีที่มีการออกจากเธรดเนื่องจากเราระบุไว้อย่างชัดเจนว่าเราไม่สนใจเข้าร่วมเธรดในภายหลังดังนั้นจึงไม่สามารถเข้าร่วมเธรดที่แยกออกแล้วได้

หัวข้อการสังหาร

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

ตามที่ระบุไว้แล้วด้ายใด ๆ ไม่ว่าจะเป็นแฝดหรือไม่จะตายกับกระบวนการของตนในระบบปฏิบัติการมากที่สุด กระบวนการนี้สามารถถูกยกเลิกได้โดยการเพิ่มสัญญาณโดยการโทรexit()หรือกลับจากฟังก์ชั่นหลัก อย่างไรก็ตาม C ++ 11 ไม่สามารถและไม่พยายามที่จะกำหนดพฤติกรรมที่แน่นอนของระบบปฏิบัติการพื้นฐานในขณะที่นักพัฒนาของ Java VM สามารถสรุปความแตกต่างดังกล่าวได้ในระดับหนึ่ง AFAIK กระบวนการที่แปลกใหม่และแบบจำลองเกลียวมักจะพบได้บนแพลตฟอร์มโบราณ (ซึ่ง C ++ 11 อาจไม่ได้รับการพอร์ต) และระบบฝังตัวที่หลากหลายซึ่งอาจมีการใช้งานไลบรารีภาษาพิเศษและ / หรือ จำกัด และสนับสนุนภาษา จำกัด

สนับสนุนเธรด

หากกระทู้ไม่สนับสนุนstd::thread::get_id()ควรกลับรหัสไม่ถูกต้อง (เริ่มต้นสร้างstd::thread::id) เป็นมีกระบวนการธรรมดาซึ่งไม่จำเป็นต้องเป็นวัตถุด้ายในการทำงานและสร้างของที่ควรจะโยนstd::thread std::system_errorนี่คือวิธีที่ฉันเข้าใจ C ++ 11 ร่วมกับระบบปฏิบัติการของวันนี้ หากมีระบบปฏิบัติการที่สนับสนุนเธรดซึ่งไม่ได้วางไข่เธรดหลักในกระบวนการของมันให้แจ้งให้เราทราบ

การควบคุมหัวข้อ

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


3
เนื่องจากทุกเธรดมีสแต็กของตัวเอง (ซึ่งอยู่ในช่วงเมกะไบต์บน Linux) ฉันจะเลือกที่จะแยกเธรด (ดังนั้นสแต็กของมันจะถูกปล่อยทันทีที่ออกจาก) และใช้การซิงค์พื้นฐานหากเธรดหลักต้องการออก (และสำหรับการปิดระบบที่เหมาะสมจะต้องเข้าร่วมเธรดที่กำลังรันอยู่แทนการยกเลิกเธรดเมื่อส่งคืน / ออก)
Norbert Bérci

8
ฉันไม่เห็นว่าสิ่งนี้ตอบคำถามได้อย่างไร
MikeMB

18

พิจารณารหัสต่อไปนี้:

#include <iostream>
#include <string>
#include <thread>
#include <chrono>

void thread_fn() {
  std::this_thread::sleep_for (std::chrono::seconds(1)); 
  std::cout << "Inside thread function\n";   
}

int main()
{
    std::thread t1(thread_fn);
    t1.detach();

    return 0; 
}

รันบนระบบ Linux ข้อความจาก thread_fn จะไม่ถูกพิมพ์ ระบบปฏิบัติการทำความสะอาดthread_fn()ทันทีที่main()ออก การแทนที่t1.detach()ด้วยt1.join()จะพิมพ์ข้อความตามที่คาดไว้เสมอ


พฤติกรรมนี้เกิดขึ้นอย่างแน่นอนบน Windows ดังนั้นดูเหมือนว่า Windows จะฆ่าเธรดที่ดึงออกมาเมื่อโปรแกรมเสร็จสิ้น
Gupta

17

ชะตากรรมของเธรดหลังจากออกจากโปรแกรมเป็นพฤติกรรมที่ไม่ได้กำหนด แต่ระบบปฏิบัติการที่ทันสมัยจะล้างเธรดทั้งหมดที่สร้างขึ้นโดยกระบวนการเมื่อปิดมัน

เมื่อถอดออกstd::threadเงื่อนไขทั้งสามนี้จะยังคงมีอยู่:

  1. *this ไม่ได้เป็นเจ้าของเธรดใด ๆ อีกต่อไป
  2. joinable() จะเท่ากับ false
  3. get_id() จะเท่ากัน std::thread::id()

1
ทำไมไม่ได้กำหนด? เพราะมาตรฐานไม่ได้กำหนดอะไรเลย? ตามเชิงอรรถของฉันนั่นจะไม่ทำให้สายใด ๆ ที่detach()มีพฤติกรรมที่ไม่ได้กำหนด? ยากที่จะเชื่อ ...
Marc Mutz - mmutz

2
@ MarcMutz-mmutz มันไม่ได้กำหนดในแง่ที่ว่าถ้ากระบวนการออกไปชะตากรรมของเธรดจะไม่ได้กำหนด
Caesar

2
@Caesar และฉันจะแน่ใจได้อย่างไรว่าไม่ออกก่อนที่เธรดจะเสร็จสิ้น
MichalH


0

เพื่ออนุญาตให้เธรดอื่นดำเนินการต่อได้เธรดหลักควรยุติโดยการเรียก pthread_exit () แทนที่จะออก (3) ไม่เป็นไรที่จะใช้ pthread_exit เป็นหลัก เมื่อใช้ pthread_exit เธรดหลักจะหยุดดำเนินการและจะยังคงอยู่ในสถานะ zombie (defunct) จนกว่าเธรดอื่นทั้งหมดจะจบการทำงาน หากคุณใช้ pthread_exit ในเธรดหลักไม่สามารถรับสถานะการส่งคืนของเธรดอื่นและไม่สามารถทำการล้างสำหรับเธรดอื่น (สามารถทำได้โดยใช้ pthread_join (3)) นอกจากนี้ยังเป็นการดีกว่าที่จะแยกเธรด (pthread_detach (3)) เพื่อให้รีซอร์สเธรดถูกปล่อยโดยอัตโนมัติเมื่อมีการยกเลิกเธรด ทรัพยากรที่ใช้ร่วมกันจะไม่ได้รับการปล่อยตัวจนกว่ากระทู้ทั้งหมดจะออก


@kgvinod ทำไมไม่เพิ่ม "pthread_exit (0);" หลังจาก "ti.detach ()";
yshi

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