C ++ ยุติการเรียกโดยไม่มีข้อยกเว้นที่ใช้งานอยู่


94

ฉันได้รับข้อผิดพลาด C ++ กับเธรด:

terminate called without an active exception
Aborted

นี่คือรหัส:

#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>

template<typename TYPE>
class blocking_stream
{
public:
    blocking_stream(size_t max_buffer_size_)
        :   max_buffer_size(max_buffer_size_)   
    {
    }

    //PUSH data into the buffer
    blocking_stream &operator<<(TYPE &other)
    {
        std::unique_lock<std::mutex> mtx_lock(mtx); 
        while(buffer.size()>=max_buffer_size)
            stop_if_full.wait(mtx_lock);

        buffer.push(std::move(other));

        mtx_lock.unlock();
        stop_if_empty.notify_one();
        return *this;
    }
    //POP data out of the buffer 
    blocking_stream &operator>>(TYPE &other)
    {
        std::unique_lock<std::mutex> mtx_lock(mtx);
        while(buffer.empty())
            stop_if_empty.wait(mtx_lock);

        other.swap(buffer.front()); 
        buffer.pop();

        mtx_lock.unlock();
        stop_if_full.notify_one();
        return *this;
    }

private:
    size_t max_buffer_size;
    std::queue<TYPE> buffer;
    std::mutex mtx;
    std::condition_variable stop_if_empty,
                            stop_if_full;
    bool eof;   
};

ฉันจำลองโค้ดของฉันตามตัวอย่างนี้: http://www.justsoftwaresolutions.co.uk/threading/implementing-a-thread-safe-queue-using-condition-variables.html

ฉันทำอะไรผิดและฉันจะแก้ไขข้อผิดพลาดได้อย่างไร


9
คุณติดตั้งjoinเธรดทั้งหมดในโปรแกรมหลักของคุณหรือไม่?
Kerrek SB

แสดงโค้ดที่เหลือให้เราดู
แมท

2
@Kerrek อาฮ่านี่แก้ปัญหาได้ แต่ฉันไม่รู้ว่าทำไมถึงมั่นใจว่าเธรดหลักไม่ได้ยุติก่อนที่คนงานจะเสร็จ alogorithms ล็อคของฉันยังดูถูกต้องไหม
111111

รหัสที่รวบรวมได้ซึ่งทำให้เกิดปัญหาขึ้นอีกครั้ง
Martin York

3
ดูเหมือนว่ารันไทม์สามารถวินิจฉัยปัญหาได้ดีกว่าในกรณีนี้?
Nemo

คำตอบ:


127

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


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

1
และคำพูดนั้นดูขัดแย้งกับคำตอบนี้: stackoverflow.com/a/3970921/148668
Alec Jacobson

5
@mangledorf: สังเกตว่าพวกเขากำลังพูดถึง aobut boost :: thread และฉันกำลังพูดถึง std :: thread สองคนนี้มีพฤติกรรมการทำลายล้างที่แตกต่างกัน นี่เป็นการตัดสินใจอย่างมีสติในส่วนของคณะกรรมการ
Bartosz Milewski

เกิดอะไรขึ้นถ้าคุณทำงานเป็นปัญหานี้ด้วยstd::async? คุณจะเข้าร่วม / แยกเธรดใด ๆ ที่อาจสร้างขึ้นที่นั่นได้อย่างไร? ดูเหมือนว่าการรอในอนาคตที่เป็นผลลัพธ์จะไม่เพียงพอเพราะสิ่งนี้บอกว่าเธรดอาจ "อาจมาจากเธรดพูล" และการรอคอยในอนาคต () ไม่ได้หมายความถึงการยุติเธรดในพูล (และนั่นจะไม่ ไม่สมเหตุสมผลสำหรับพูลเธรดที่มีเหตุผล)
Jason C

2
เพียงแค่การอัปเดตที่ใน C ++ 20 std::jthreadจะเรียกใช้.join()ในตัวทำลาย ซึ่งส่วนตัวชอบดีกว่าเพราะตาม RAII ดีกว่า
pooya13

46

วิธีทำให้เกิดข้อผิดพลาดนั้น:

#include <iostream>
#include <stdlib.h>
#include <string>
#include <thread>
using namespace std;
void task1(std::string msg){
  cout << "task1 says: " << msg;
}
int main() { 
  std::thread t1(task1, "hello"); 
  return 0;
}

รวบรวมและเรียกใช้:

el@defiant ~/foo4/39_threading $ g++ -o s s.cpp -pthread -std=c++11
el@defiant ~/foo4/39_threading $ ./s
terminate called without an active exception
Aborted (core dumped)

คุณได้รับข้อผิดพลาดนั้นเนื่องจากคุณไม่ได้เข้าร่วมหรือถอดชุดข้อความของคุณ

วิธีหนึ่งในการแก้ไขเข้าร่วมกระทู้ดังนี้:

#include <iostream>
#include <stdlib.h>
#include <string>
#include <thread>
using namespace std;
void task1(std::string msg){
  cout << "task1 says: " << msg;
}
int main() { 
  std::thread t1(task1, "hello"); 
  t1.join();
  return 0;
}

จากนั้นรวบรวมและเรียกใช้:

el@defiant ~/foo4/39_threading $ g++ -o s s.cpp -pthread -std=c++11
el@defiant ~/foo4/39_threading $ ./s
task1 says: hello

วิธีอื่นในการแก้ไขให้ถอดออกดังนี้:

#include <iostream>
#include <stdlib.h>
#include <string>
#include <unistd.h>
#include <thread>
using namespace std;
void task1(std::string msg){
  cout << "task1 says: " << msg;
}
int main() 
{ 
     {

        std::thread t1(task1, "hello"); 
        t1.detach();

     } //thread handle is destroyed here, as goes out of scope!

     usleep(1000000); //wait so that hello can be printed.
}

รวบรวมและเรียกใช้:

el@defiant ~/foo4/39_threading $ g++ -o s s.cpp -pthread -std=c++11
el@defiant ~/foo4/39_threading $ ./s
task1 says: hello

อ่านข้อมูลเกี่ยวกับการแยกเธรด C ++ และการเข้าร่วมเธรด C ++


1
ในบริบทนี้การใช้ usleep () จะสมเหตุสมผลก็ต่อเมื่อเธรดถูกแยกออกและแฮนเดิลถูกทำลาย (โดยการออกนอกขอบเขต) ดังนั้นฉันจึงแก้ไขรหัสของคุณเพื่อให้สอดคล้องกับสิ่งนี้
Nawaz

17

Eric Leschinski และ Bartosz Milewski ได้ให้คำตอบแล้ว ในที่นี้จะพยายามนำเสนอในรูปแบบที่เป็นมิตรกับผู้เริ่มต้นมากขึ้น

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

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

หมายเหตุเมื่อถึงเวลาที่เธรดเข้าร่วมหรือแยกออกจากกันเธรดอาจดำเนินการเสร็จสิ้นแล้ว ยังคงต้องดำเนินการอย่างใดอย่างหนึ่งจากสองอย่างอย่างชัดเจน


1

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

int main(){

std::thread t(thread,1);

while(1){}

//t.detach();
return 0;}

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

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

void thread(int n) {std::this_thread::sleep_for (std::chrono::seconds(n));}

int main() {
std::cout << "Start main\n";
std::thread t(thread,1);
std::thread t2(thread,3);
std::thread t3(thread,8);
sleep(5);

t.detach();
t2.detach();
t3.detach();
return 0;}

1

ปีเธรดจะต้องเข้าร่วม () เมื่อออกหลัก


1
คำตอบนี้น่าจะเหมาะกับความคิดเห็นที่แนบมากับคำตอบอื่นมากกว่า และฉันต้องบอกว่ายินดีต้อนรับสู่ Stack Overflow!
Contango

0

ก่อนอื่นคุณกำหนดเธรด และถ้าคุณไม่เคยเรียก join () หรือ detach () ก่อนที่จะเรียก thread destructor โปรแกรมจะยกเลิก

ดังต่อไปนี้การเรียกใช้เธรด destructor โดยไม่ต้องเรียก join ก่อน (เพื่อรอให้เสร็จสิ้น) หรือ detach จะต้องเรียกใช้ std :: ทันทีและสิ้นสุดโปรแกรม

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

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