Sam
Sam

Reputation: 23

Segmentation Fault when calling class functions in async

I am a beginner at c++ and was messing around with dynamic memory and asynchronous functions and I was not sure why the following code did not work.

What I wanted to do is to basically dynamically create an object of Loop and an async function which calls the start method of the object, each assigning to a different map variable with the same id as key for both (randomthing in the example code).

Expected: print out numbers 10 to 1 every second.

Result: Segmentation Fault

#include <iostream>
#include <vector>
#include <map>
#include <chrono>
#include <future>

class Loop{
    public:
    Loop(int t_):t(t_){}
    void start(){
        while(t>0){
            std::cout << t << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
            t--;
        }
    }
    
    private:
    int t;
};

int main()
{
    std::map<std::string, std::future<void>> store;
    std::map<std::string, Loop*> storeClass;
    storeClass["randomthing"] = new Loop(10);
    store["randomthing"] = std::async(std::launch::async, [&storeClass](){
        std::cout << "starting" << std::endl;
        storeClass["randomthing"]->start(); 
        std::cout << "finished" << std::endl;
        delete storeClass["randomthing"];
    });
    return 0;
}

I messed around it a bit more and found that this modification works:

Declaring store as std::future<void> store instead of a map and directly assigning the async function to it after creating the Loop object.

Why the example code does not work and why can the modification fix the issue?

I am compiling the program with -std=c++11.

Upvotes: 0

Views: 812

Answers (1)

KamilCuk
KamilCuk

Reputation: 140960

You have to make sure that all variables are valid within the lifetime when they are used. Your code:

    std::async(std::launch::async, [&storeClass](){
        storeClass["randomthing"]->start(); // uses storeClass
    });
    return 0;
} // calls storeClass destructor

Has a race condition, where storeClass destructor and use of storeClass are unsequenced. In result, when destructor happens to be called after async, then it's fine, otherwise you can get a seg fault.

Do not use raw pointers - use std::unique_ptr for Loop*.

To solve it you could make storeClass a std::shared_ptr or unique_ptr and copy/move it into the lambda run in async. Or just wait for the async to complete:

    store["randomthing"] = std::async(std::launch::async, [&storeClass](){
         .. use storeClass ..
    });
    store["randomthing"].wait(); // wait for calling `storeClass["randomthing"]
    return 0; 
} // will destroy storeClass

Upvotes: 1

Related Questions