简体   繁体   中英

Proper usage of std::promise and std::future, segfaults

I've been trying my luck on a small threadpool implementation. However, after conceptualizing and implementing i've hit a brick wall. I've confirmed that the worker threads ate starting up and sleeping correctly, also that they pick up and execute stored tasks correctly. However, my program segfaults - i'm pretty sure its at promise.set_value .

Im not sure how i could provide a complete, verifiable example (given that i can hardly upload the whole code) but i'll include the segments i believe to be relevant to this problem. First off, workers are created like this:

worker = [this](){
    while(true)
    {
        std::unique_lock<std::mutex> lock(mStatusMutex); //CV for status updates
        mCV.wait(lock);
        if(mStatus != Running) //If threadpool status does not imply running
            break; //Break out of loop, ending thread in the process
        else //If threadpool is in running state
        {
            lock.unlock(); //Unlock state
            while(true) //Loop until no tasks are left
            {
                mTasksMutex.lock(); //Lock task queue
                if(mTasks.empty()) //IF no tasks left, break out of loop and return to waiting
                {
                    mTasksMutex.unlock();
                    break;
                }
                else //Else, retrieve a task, unlock the task queue and execute the task
                {
                    std::function<void()> task = mTasks.front();
                    mTasks.pop();
                    mTasksMutex.unlock();
                    task(); //Execute task
                }
            }
        }
    }
};

And then started and stored into a std::vector<std::thread> like this:

std::thread tWorker(worker);
mWorkers.push_back(std::move(tWorker));

Now, the tricky part i believe to be the following is when adding/executing tasks to the task queue, which is a std::queue<std::function<void()>> . The following two functions are relevant here:

template<typename RT>
inline std::future<RT> queueTask(std::function<RT()> _task, bool _execute = false)
{
    std::promise<RT> promise;
    std::function<void()> func([&_task, &promise]() -> RT {
        RT val = _task();
        promise.set_value(val);
    });

    mTasksMutex.lock();
    mTasks.emplace(func);
    mTasksMutex.unlock();
    if(_execute) flush();
    return promise.get_future();
}
inline void flush()
{
    mCV.notify_all();
}

Is there anything principally wrong with this approach? For anyone who believes this to be a bad question, feel free to tell me how i can improve it. Full code is hosted on my github repo .

The main problem is that the promise is already dead. When queueTask is done, the promise is destroyed, and the task now just has a dangling reference. The task must share ownership of the promise in order for it to live long enough to fulfill it.

The same is true of the underlying std::function object _task , since you're capturing it by reference.

You're using std::function , which requires copyable objects, hence... shared_ptr :

template<typename RT>
inline std::future<RT> queueTask(std::function<RT()> _task, bool _execute = false)
{
    auto promise = std::make_shared<std::promise<RT>>();
    std::function<void()> func([promise, task=std::move(_task)]{
        RT val = _task();
        promise->set_value(val);
    });

    {
        std::lock_guard<std::mutex> lk(mTasksMutex); // NB: no manual lock()/unlock()!!
        mTasks.emplace(func);
    }
    if(_execute) flush();
    return promise->get_future();
}

Consider std::packaged_task instead.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM