เมื่อใดที่ฉันควรใช้ std :: thread :: detach


140

บางครั้งฉันต้องใช้std::threadเพื่อเร่งความเร็วการสมัครของฉัน ฉันก็รู้เช่นกันjoin()รอจนกว่าเธรดจะเสร็จสมบูรณ์ สิ่งนี้ง่ายต่อการเข้าใจ แต่ความแตกต่างระหว่างการโทรdetach()และไม่เรียกมันคืออะไร

ฉันคิดว่าไม่มีdetach()วิธีของเธรดจะทำงานโดยใช้เธรดอย่างอิสระ

ไม่ถอด:

void Someclass::Somefunction() {
    //...

    std::thread t([ ] {
        printf("thread called without detach");
    });

    //some code here
}

การโทรด้วยการถอดออก:

void Someclass::Somefunction() {
    //...

    std::thread t([ ] {
        printf("thread called with detach");
    });

    t.detach();

    //some code here
}


ทั้งสองstdและboostเธรดมีdetachและjoinทำโมเดลอย่างใกล้ชิดหลังจากเธรด POSIX
n คำสรรพนาม 'm

คำตอบ:


149

ใน destructor ของstd::thread, std::terminateที่เรียกว่าถ้า:

  • เธรดไม่ได้เข้าร่วม (ด้วยt.join())
  • และไม่ถูกแยกออกด้วย (เช่นt.detach())

ดังนั้นคุณควรจะอย่างใดอย่างหนึ่งjoinหรือdetachเธรดก่อนที่กระแสของการดำเนินการถึง destructor


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

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


ดังนั้นคุณควรใช้joinหรือdetach?

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

ถ้าฉันจะเรียก pthread_exit (NULL); ใน main () จากนั้น exit () จะไม่ถูกเรียกจาก main () และด้วยเหตุนี้โปรแกรมจะทำงานต่อไปจนกว่าเธรดเดี่ยวทั้งหมดจะเสร็จสมบูรณ์ จากนั้นจะเรียก exit ()
southerton

1
@ Matthieu ทำไมเราไม่สามารถเข้าร่วมใน destructor ของ std :: thread?
john smith

2
@johnsmith: เป็นคำถามที่ยอดเยี่ยม! จะเกิดอะไรขึ้นเมื่อคุณเข้าร่วม คุณรอจนกว่าเธรดจะเสร็จสมบูรณ์ หากมีข้อผิดพลาดเกิดขึ้นจะมีการดำเนินการ destructors ... และทันใดนั้นการแพร่กระจายของข้อยกเว้นของคุณจะถูกระงับจนกว่าเธรดจะสิ้นสุดลง มีหลายสาเหตุที่ทำให้ไม่ควรสังเกตว่ากำลังรออินพุตจากเธรดที่ถูกระงับชั่วคราวในขณะนี้! ดังนั้นผู้ออกแบบจึงเลือกให้เป็นทางเลือกที่ชัดเจนแทนที่จะเลือกข้อโต้แย้งที่เป็นค่าเริ่มต้น
Matthieu M.

@ Matthieu ฉันคิดว่าคุณหมายถึง call join () ก่อน std :: ถึงตัวทำลายเธรด คุณสามารถ (และควร?) เข้าร่วม () ตัวทำลายของคลาสที่ปิดล้อมหรือไม่?
Jose Quinteiro

4
@JoseQuinteiro: ที่จริงแล้วไม่เหมือนแหล่งข้อมูลอื่น ๆ ขอแนะนำว่าอย่าเข้าร่วมกับผู้ทำลายล้าง ปัญหาคือการเข้าร่วมไม่ได้จบเธรดเพียงรอให้มันจบและไม่เหมือนที่คุณมีสัญญาณในการที่จะทำให้เธรดจบคุณอาจจะรอเป็นเวลานาน ... การปิดกั้นเธรดปัจจุบันที่มีสแต็กอยู่ กำลังคลายและป้องกันไม่ให้เธรดปัจจุบันหยุดการบล็อกดังนั้นรอเธรด ฯลฯ ... ดังนั้นถ้าคุณแน่ใจว่าคุณสามารถหยุดเธรดที่กำหนดในระยะเวลาที่เหมาะสมจะเป็นการดีที่สุดที่จะไม่รอ มันอยู่ใน destructor
Matthieu M.

25

คุณควรโทรdetachถ้าคุณจะไม่รอให้เธรดดำเนินการให้เสร็จjoinแต่เธรดจะทำงานต่อไปจนกว่าจะเสร็จสิ้นและจะสิ้นสุดโดยไม่ต้องรอเธรดหลัก

detachโดยทั่วไปจะปล่อยทรัพยากรที่จำเป็นเพื่อให้สามารถใช้งานjoinได้

มันเป็นข้อผิดพลาดร้ายแรงถ้าวัตถุด้ายจบชีวิตของมันและไม่joinหรือdetachได้รับการเรียก; ในกรณีนี้terminateถูกเรียกใช้


12
คุณควรจะพูดถึงว่ายุติที่เรียกกันใน destructor ถ้าด้ายได้รับค่าเข้าร่วมมิได้แฝด
nosid

11

เมื่อคุณถอดด้ายก็หมายความว่าคุณจะได้ไม่ต้องมันก่อนที่จะออกjoin()main()

ไลบรารีเธรดจะรอเธรดแต่ละอันที่อยู่ด้านล่างหลักแต่คุณไม่ควรสนใจ

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


นี่ไม่ได้ตอบคำถาม คำตอบนั้นโดยทั่วไประบุว่า "คุณแยกออกเมื่อคุณแยกออก"
rubenvb

7

คำตอบนี้มีวัตถุประสงค์ที่จะตอบคำถามในชื่อมากกว่าการอธิบายความแตกต่างระหว่างและjoin detachดังนั้นstd::thread::detachควรใช้เมื่อใด

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

อย่างไรก็ตามแอพพลิเคชั่นบางตัวนั้นใช้ API แบบเก่าและไม่ได้รับการออกแบบมาอย่างดีและรองรับ API ที่อาจมีฟังก์ชั่นการบล็อกอย่างไม่มีกำหนด การย้ายการเรียกใช้ฟังก์ชันเหล่านี้ไปยังเธรดเฉพาะเพื่อหลีกเลี่ยงการบล็อกสิ่งอื่น ๆ ถือเป็นเรื่องธรรมดา ไม่มีวิธีในการสร้างเธรดดังกล่าวเพื่อออกอย่างสง่างามดังนั้นการใช้งานjoinจะนำไปสู่การบล็อกเธรดหลัก นั่นเป็นสถานการณ์ที่การใช้งานdetachจะเป็นทางเลือกที่เลวร้ายกว่าการพูดการจัดสรรthreadออบเจ็กต์ด้วยระยะเวลาการจัดเก็บข้อมูลแบบไดนามิกและจงใจปล่อยมัน

#include <LegacyApi.hpp>
#include <thread>

auto LegacyApiThreadEntry(void)
{
    auto result{NastyBlockingFunction()};
    // do something...
}

int main()
{
    ::std::thread legacy_api_thread{&LegacyApiThreadEntry};
    // do something...
    legacy_api_thread.detach();
    return 0;
}

1

ตามcppreference.com :

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

หลังจากเรียกแยกออกจะ*thisไม่ได้เป็นเจ้าของเธรดใด ๆ อีกต่อไป

ตัวอย่างเช่น:

  std::thread my_thread([&](){XXXX});
  my_thread.detach();

สังเกตุตัวแปรท้องถิ่น: my_threadในขณะที่อายุการใช้งานของmy_threadสิ้นสุดลงผู้ทำลายล้างstd::threadจะถูกเรียกใช้และstd::terminate()จะถูกเรียกใช้ภายในผู้ทำลายล้าง

แต่ถ้าคุณใช้detach()คุณไม่ควรใช้my_threadอีกต่อไปแม้ว่าอายุการใช้งานของmy_threadจะสิ้นสุดลงจะไม่มีอะไรเกิดขึ้นกับเธรดใหม่


ตกลงฉันจะกลับสิ่งที่ฉันพูดตอนนี้ @ TobySpeight
DinoStray

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