C++ 线程结束

C++ thread ending

提问人:Paul 提问时间:8/23/2023 最后编辑:Paul 更新时间:8/24/2023 访问量:82

问:

我正在尝试学习一些正确的线程管理,但我在这个人为的例子中遇到了一些问题。这个想法是“Holder”类可以启动一个作业,该作业是“Wait”类的实例,该类触发线程做一些事情(等待一段时间),然后在“Holder”中调用回调来做一些清理或其他什么。 我将“等待”实例放在 std::vector 中,当回调告诉我它完成时。我将其从列表中删除。

更新:更正了注释掉的行

waitlist.erase(it);转储等待对象

如果我调用上面的行来指示作业已完成并将其从“列表”中删除,则会导致主线程过早退出,为什么?删除它的正确方法是什么?

#include <chrono>
#include <ctime>
#include <functional>
#include <iostream>
#include <thread>
#include <vector>

using Time = std::chrono::system_clock;
using Seconds = std::chrono::seconds;
using Timepoint = Time::time_point;

class Wait {
private:
    Timepoint target;
    std::thread thread;

public:
    Wait(unsigned int waitFor, unsigned short id, std::function<void(unsigned int)> callback_)
    {
        std::cout << "new Wait objet needs to wait for: " << waitFor << " seconds" << std::endl;
        target = Time::now() + Seconds(waitFor);

        // Create a thread to notify us when complete

        thread = std::thread([callback_, waitFor, id]() {
            std::this_thread::sleep_for(Seconds(waitFor));
            std::cout << "from thread: " << std::this_thread::get_id() << std::endl;
            callback_(id);
        });
        
    }

    bool isDone() 
    {
        Timepoint now = Time::now();
        std::chrono::duration<float> difference = now - target;
        return (difference.count() > 0); // Thanks @Jesper
    }
};

class Holder {
private:
    std::vector<Wait> waitlist;

public:
    unsigned short addTask(unsigned int waitFor)
    {
        unsigned int sz = waitlist.size();
        waitlist.push_back(Wait(waitFor, sz, std::bind(&Holder::callback, this, std::placeholders::_1)));

        return waitlist.size() - 1;
    }

    bool isDone(unsigned int n) 
    {
        if (waitlist.size() >= n) {
            return waitlist.at(n).isDone();
        } 
        else return false; // failed
    }

    void callback(unsigned int id)
    {
        std::cout << "All done with Wait object" << std::endl;
        
        auto it = waitlist.begin() + id;
        /* comment/uncomment next line*/
        //waitlist.erase(it); // dump the wait object
    }
};

int main() 
{
    Holder h;
    // Create a task which spawns a thread, which notifies us when complete
    unsigned int id1 = h.addTask(3);
    
    // Just to show that while main thread is running, the completion status
    // of thread changes
    for (auto c = 0; c < 5; c++)
    {
        std::this_thread::sleep_for(Seconds(1));
        std::cout << "from main: " << std::this_thread::get_id() << " - " << h.isDone(id1) << std::endl;
    }

    // Just to show the main thread continues on. It doesn't when line mentioned 
    // earlier is uncommented
    while(1) {
        std::this_thread::sleep_for(Seconds(1));
        std::cout << "Doing other stuff.." << std::endl;
    }

    return 0;
}

预期输出:

new Wait objet needs to wait for: 3 seconds
from main: 140234566031168 - 0
from main: 140234566031168 - 0
from thread: 140234566026816
All done with Wait object
from main: 140234566031168 - 1
from main: 140234566031168 - 1
from main: 140234566031168 - 1
Doing other stuff..
Doing other stuff..
Doing other stuff..
Doing other stuff..
Doing other stuff..

任何关于我忽略的(我确定是基本)前提的见解/指导将不胜感激。

保罗

C++ 多线程 回调

评论

3赞 273K 8/23/2023
请发布一个最小的可重现示例。 在显示的代码中未调用。不要让 SO 用户猜测你在哪里称呼它。waitlist.pop_back();
1赞 Jesper Juhl 8/23/2023
请记住,如果你不使用你的线程,那么当你的进程(主线程)退出时,就不能保证它们已经完成运行(甚至已经开始运行),当主进程终止时,你的线程会发生什么是实现定义的(在 Windows 上,子线程继续运行,在 Linux 上,它们被强制终止 - 例如)——始终是你的线程。.join()join
2赞 Ted Lyngmo 8/23/2023
在 C++20 中,您可以使用 s 自动强制执行@JesperJuhl提到的 s。std::jthreadjoin()
1赞 Jesper Juhl 8/23/2023
"if (difference.count() > 0) return true; else return false;“ - 过于冗长;只。return difference.count() > 0;
1赞 Paul 8/23/2023
谢谢@jesper。在阅读有关 chrono 文字的信息时,我看不出如何将它们后缀到变量名称并让它们工作,所以我只是做了暂时有效的方法。即“Seconds(waitFor)”有效,但我无法让“(waitFor)s”工作,所以我一定错过了语法问题。

答:

0赞 Paul 8/24/2023 #1

还行 我明白我做错了什么。回调(从 Wait 对象中的实例化线程执行)试图删除容纳线程的 Wait 对象,该对象调用线程的析构函数并导致主线程存在。

使用 std::async 可以解决连接/分离问题(类似于上面提到的 C++ 的 std::jthread 选项),但您可以简单地检查它是否完成了类似的东西;

if (async_task.wait_for(std::chrono::seconds(0)) == std::future_status::ready) // async done

一旦我有办法检查它是否完成,我可以简单地检查它是否完成,然后从“Holder”类中的向量中删除任务。

我不能对线程做同样的事情,因为如果我使用 join(),我将不得不等待它并且我不想要阻塞调用,如果我使用 detach(),它会将其与主实例解耦,我不知道何时/是否完成。如果实例化的线程在没有看到其中任何一个的情况下终止,则会导致主线程终止,这就是为什么我没有进入“做其他事情......”线程终止后的部分。