db7638
db7638

Reputation: 410

"Project.exe has triggered a breakpoint" after implementing multithreading

I have a Visual Studio project that worked fine until I tried to implement multithreading. The project acquires images from a GigE camera, and after acquiring 10 images, a video is made from the acquired images.

The program flow was such that the program didn't acquire images when it was making the video. I wanted to change this, so I created another thread that makes the videos from the images. What I wanted is that the program will acquire images continuously, after 10 images are acquired, another thread runs in parallel that will make the video. This will continue until I stop the program, 10 images are acquired, video from these 10 images is made in parallel while the next 10 images are acquired and so on.

I haven't created threads before so I followed the tutorial on this website. Similar to the website, I created a thread for the function that saves the video. The function that creates the video takes the 10 images as a vector argument. I execute join on this thread just before the line where my main function terminates.

For clarity, here's pseudo-code for what I've implemented:

#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;
}

When I run the project now, a message pops up saying that the Project.exe has triggered a breakpoint. The project breaks in report_runtime_error.cpp in static bool __cdecl issue_debug_notification(wchar_t const* const message) throw().

I'm printing some cout messages on the console to help me understand what's going on. What happens is that the program acquires 10 images, then the thread for saving the video starts running. As there are 10 images, 10 images have to be appended to the video. The message that says Project.exe has triggered a breakpoint pops up after the second time 10 images are acquired, at this point the parallel thread has only appended 6 images from the first acquired set of images to the video.

The output contains multiple lines of thread XXXX has exited with code 0, after that the output says

Debug Error!

Program: ...Path\Program.exe

abort() has been called

(Press Retry to debug the application)

Program.exe has triggered a breakpoint.

Upvotes: 0

Views: 3780

Answers (2)

Caleth
Caleth

Reputation: 63152

You are overwriting your one thread in the while loop. If it's still running, the program is aborted. You have to join or detach each thread value.

You could instead do

#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;
}

Upvotes: 1

user4581301
user4581301

Reputation: 33952

I can't explain all this in a comment. I'm dropping this here because it looks like OP is heading in some bad directions and I'd like to head him off before the cliff. Caleth has caught the big bang and provided a solution for avoiding it, but that bang is only a symptom of OP's and the solution with detach is somewhat questionable.

using namespace std;

Why is "using namespace std" considered bad practice?

thread threads[1];

An array 1 is pretty much pointless. If we don't know how many threads there will be, use a vector. Plus there is no good reason for this to be a global variable.

vector<Image> images;

Again, no good reason for this to be global and many many reasons for it NOT to be.

void thread_method(vector<Image> & images){

Pass by reference can save you some copying, but A) you can't copy a reference and threads copy the parameters. OK, so use a pointer or std::ref. You can copy those. But you generally don't want to. Problem 1: Multiple threads using the same data? Better be read only or protected from concurrent modification. This includes the thread generating the vector. 2. Is the reference still valid?

    // 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)

Bad for reasons Caleth covered. Plus images keeps growing. The first thread, even if copied, has ten elements. The second has the first ten plus another ten. This is weird, and probably not what OP wants. References or pointers to this vector are fatal. The vector would be resized while other threads were using it, invalidating the old datastore and making it impossible to safely iterate.

    }

    // stuff

    threads[0].join();

Wrong for reasons covered by Caleth

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

    return 0;
}

The solution to joining on the threads is the same as just about every Stack Overflow question that doesn't resolve to "Use a std::string": Use a 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;
}

To better explain

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

this created a thread inside threads (emplace_back) that will call thread_method with a copy of images. Odds are good that the compiler would have recognized that this was the end of the road for this particular instance of images and eliminated the copying, but if not, std::move should give it the hint.

Upvotes: 1

Related Questions