实现多线程后“Project.exe 已触发断点”

Posted

技术标签:

【中文标题】实现多线程后“Project.exe 已触发断点”【英文标题】:"Project.exe has triggered a breakpoint" after implementing multithreading 【发布时间】:2017-07-27 20:53:31 【问题描述】:

在我尝试实现 multithreading 之前,我有一个运行良好的 Visual Studio 项目。该项目从GigE摄像头获取图像,获取10张图像后,将获取的图像制作成视频。

程序流程使得程序在制作视频时没有获取图像。我想改变这一点,所以我创建了另一个从图像制作视频的线程。我想要的是程序将连续获取图像,在获取 10 张图像后,另一个线程并行运行将制作视频。这将一直持续到我停止程序,获取 10 张图像,从这 10 张图像中并行制作视频,同时获取接下来的 10 张图像,依此类推。

我之前没有创建过threads,所以我按照this website 上的教程进行操作。与网站类似,我为保存视频的function 创建了一个thread。创建视频的 function 将 10 张图像作为 vector 参数。我在这个线程上执行join,就在我的main函数终止的那一行之前。

为了清楚起见,这里是我实现的伪代码:

#include ...
#include <thread>

using namespace std;

thread threads[1];
vector<Image> images;

void thread_method(vector<Image> & images)
    // Save vector of images to video
    // Clear the vector of images


int main(int argc, char* argv[])
    // Some stuff
    while(TRUE)
    
        for (int i = 0; i < 10; i++)
            
            //Acquire Image
            //Put image pointer in images vector named images
        
        threads[0] = thread(thread_method, images)
    

    // stuff

    threads[0].join();

    cout << endl << "Done! Press Enter to exit..." << endl;
    getchar();

    return 0;

当我现在运行项目时,会弹出一条消息说 Project.exe 已触发断点。该项目在report_runtime_error.cpp 中中断static bool __cdecl issue_debug_notification(wchar_t const* const message) throw()

我正在控制台上打印一些cout 消息,以帮助我了解发生了什么。发生的情况是程序获取了 10 张图像,然后用于保存视频的 thread 开始运行。由于有 10 张图像,因此必须将 10 张图像附加到视频中。第二次获取10张图片后,弹出Project.exe已触发断点的消息,此时并行线程仅将第一次获取的一组图片中的6张图片附加到视频中。

输出包含多行thread XXXX has exited with code 0,之后输出显示

Debug Error!

Program: ...Path\Program.exe

abort() has been called

(Press Retry to debug the application)

Program.exe has triggered a breakpoint.

【问题讨论】:

@Ron 我已将runStatus 替换为TRUE,将result 替换为0,并为images 的定义添加了一行。 Image 是 API(相机 API)中的一个类,遗憾的是它不公开。 【参考方案1】:

我无法在评论中解释这一切。我把它放在这里是因为看起来 OP 正朝着一些不好的方向前进,我想在悬崖前把他带走。 Caleth 捕捉到了大爆炸并提供了避免它的解决方案,但大爆炸只是 OP 的症状,detach 的解决方案有点值得怀疑。

using namespace std;

Why is "using namespace std" considered bad practice?

thread threads[1];

数组 1 几乎毫无意义。如果我们不知道会有多少线程,请使用vector。另外,没有充分的理由将其作为全局变量。

vector<Image> images;

再说一次,没有充分的理由让它成为全球性的,而且有很多理由让它不是。

void thread_method(vector<Image> & images)

通过引用传递可以为您节省一些复制,但是 A)您不能复制引用并且线程复制参数。好的,所以使用指针或std::ref。你可以复制那些。但你通常不想这样做。问题1:多个线程使用相同的数据?最好是只读或防止并发修改。这包括生成vector 的线程。 2. 引用还有效吗?

    // Save vector of images to video
    // Clear the vector of images


int main(int argc, char* argv[])
    // Some stuff
    while(TRUE)
    
        for (int i = 0; i < 10; i++)
            
            //Acquire Image
            //Put image pointer in images vector named images
        
        threads[0] = thread(thread_method, images)

由于 Caleth 所涵盖的原因而不好。加上images 不断增长。第一个线程,即使被复制,也有十个元素。第二个有前十个加上另外十个。这很奇怪,可能不是 OP 想要的。对此vector 的引用或指针是致命的。 vector 将在其他线程使用它时调整大小,从而使旧数据存储无效并使其无法安全地迭代。

    

    // stuff

    threads[0].join();

由于 Caleth 所涵盖的原因而出错

    cout << endl << "Done! Press Enter to exit..." << endl;
    getchar();

    return 0;

加入线程的解决方案与几乎所有未解决为“使用std::string”的堆栈溢出问题相同:使用std::vector

#include <iostream>
#include <vector>
#include <thread>


void thread_method(std::vector<int> images)
    std::cout << images[0] << '\n'; // output something so we can see work being done.
    // we may or may not see all of the numbers in order depending on how 
    // the threads are scheduled.    


int main() // not using arguments, leave them out.

    int count = 0; // just to have something to show

    // no need for threads to be global.
    std::vector<std::thread> threads; // using vector so we can store multiple threads

    // Some stuff
    while(count < 10) // better-defined terminating condition for testing purposes
    
        // every thread gets its own vector. No chance of collisions or duplicated 
        // effort
        std::vector<int> images; // don't have Image, so stubbing it out with int
        for (int i = 0; i < 10; i++)
        
            images.push_back(count);
        
        // create and store thread.
        threads.emplace_back(thread_method,std::move(images));
        count ++;
    

    // stuff

    for (std::thread &temp: threads) // wait for all threads to exit
    
        temp.join();
    

    // std::endl is expensive. It's a linefeed and s stream flush, so save it for 
    // when you really need to get a message out immediately
    std::cout << "\nDone! Press Enter to exit..." << std::endl; 

    char temp;
    std::cin >>temp; // sticking with standard librarly all the way through

    return 0;

为了更好的解释

threads.emplace_back(thread_method,std::move(images));

这在threads (emplace_back) 内部创建了一个thread,它将调用thread_method 并带有images 的副本。很有可能编译器会认识到这是 images 的这个特定实例的终点并消除了复制,但如果没有,std::move 应该给出提示。

【讨论】:

除了我所要求的内容之外,此答案还包含许多有用的信息。从该图像矢量制作视频后,我是否仍应清除 thread_method 中的图像矢量?我认为这并不重要,因为我现在正在传递一份副本? 无需清除。它将在函数退出时被销毁,释放所有资源。 while 循环中的源 vector 也将被销毁并在每个循环中重新创建,因此无需在那里清除。【参考方案2】:

你是 overwriting 你在 while 循环中的一个线程。如果它仍在运行,则程序被中止。您必须加入或分离每个线程值。

你可以这样做

#include // ...
#include <thread>

// pass by value, as it's potentially outliving the loop
void thread_method(std::vector<Image> images)
    // Save vector of images to video


int main(int argc, char* argv[])
    // Some stuff
    while(TRUE)
    
        std::vector<Image> images; // new vector each time round

        for (int i = 0; i < 10; i++)
            
            //Acquire Image
            //Put image pointer in images vector named images
        
        // std::thread::thread will forward this move
        std::thread(thread_method, std::move(images)).detach(); // not join
    

    // stuff

    // this is somewhat of a lie now, we have to wait for the threads too
    std::cout << std::endl << "Done! Press Enter to exit..." << std::endl;
    std::getchar();

    return 0;

【讨论】:

您可能需要更改为std::vector&lt;std::unique_ptr&lt;Image&gt;&gt; 并将其移动到线程中,以避免复制图像 这成功了!虽然现在我遇到了另一个不相关的错误,但是是时候进行更多调试了! Caleth:看看std::ref 作为复制问题的可能解决方案。但是... zoinks!你想要副本。 @db7638 认真重新考虑您在这里尝试做的事情。通过引用向量,每个线程都使用相同的向量。早期线程将使用向量 元素正在为以后的线程添加。矢量调整大小将是致命的,因为一个线程正在检查的数据将在使用中消失。另外,让许多线程同时处理相同的数据是非常糟糕的。 其实,刮一下unique_ptr,我刚刚意识到你需要的只是std::move进入线程构造函数

以上是关于实现多线程后“Project.exe 已触发断点”的主要内容,如果未能解决你的问题,请参考以下文章

多线程:(充分利用定义任务后,开启多线程实现任务的理解)题目:模拟三个老师同时给50个小朋友发苹果,每个老师相当于一个线程。

利用spring实现多线程 + 事务回滚

Java多线程实现异步调用

Linux多线程编程

多线程实现并发请求

Java实现多线程的三种方式