std::thread 线程何时执行?

4

我看过一些关于线程的教程,但有一个问题让我很好奇。

std::thread Child([](){ std::cout << "When am I executed?" << std::endl << std::endl; });

//Some other code

Child.join();

//I'm guessing now my thread would be running

当我调用join()时,线程是否正在执行?它是在创建线程和调用join之间的某个时间运行吗?如果它在调用join()时被执行,为了检查我的理解,它会告诉那一部分去执行并且你的程序继续在主线程上运行,最终子线程在使用与主线程相同的内存上完成了一些工作吗?
如果我想为一个通用类创建一个包装器,我想做以下操作,但我似乎无法完全弄清楚。在管理线程方面,我感到困惑。
class Sync {
private:
    int val;

public:
    Sync() : val(0) {}
    void Inc() {val++}
    int Value() { return val; }
};

class Async {
private:
    Sync Foo;
    std::mutex mtx;
    std::vector<std::thread*> Children;
public:
    //I would need a new thread each time I called Inc
    void Inc() { 
        Children.push_back(new std::thread([&]() {
            mtx.lock();
            Foo.Inc();
            mtx.unlock();
        }));


    }
    //But how do I know when it is safe to delete Child?  
    int Value() { 
        for(auto& thds : Children) {
            thds->join();
            delete thds; 
        }
        Children.clear();
        return Foo.Value(); }
};

我在考虑一个合适的位置,可能是在线程函数的结尾处,因为Child将不再需要,但如果您尝试从线程内部销毁线程会发生什么呢?我猜这听起来就像一个坏主意一样。如何知道何时可以删除我的线程?有更好的方法吗?
修改上述代码以反映下面的建议。
我现在意识到教程中所说的关于抛出异常的内容,所以我应该使用互斥锁保护而不是mtx.lock()。
4个回答

8
join 的目的是等待线程完成。所以一旦 Child->join() 返回,你的线程就已经完成了。当然,你也可以在析构函数中执行 Child->join() ,或者在其他时间点执行,只要确保它在某个时间点被调用即可。
请注意,线程会在创建和 join 结束之间的某个时间点开始运行。无法确定何时会发生这种情况。如果系统上已经有大量线程正在运行,则时间将分配给所有线程,并且你的线程可能不会运行几秒钟。另一方面,如果有一个 CPU "闲着没事干",它很可能在主线程还没有退出 new std::thread(...) 构造函数之前就已经开始了(因为 std::thread 不会返回,直到线程已经被正确地创建,并且一些数据存储在线程对象中,所以在到达 Child->join() 时,线程可能已经完成)。在不知道系统状态的情况下,无法判断这些选项中的哪一个。

线程一旦创建就会立即开始运行吗?此外,在调用join后,我可以调用delete。谢谢。 - Chemistpp
2
是的,我刚刚编辑了答案以澄清线程可能在您完成创建之前开始运行,或者在稍后某个时间点开始。这完全取决于系统中有多少个CPU/核心以及系统负载是什么。 - Mats Petersson

5
操作系统很重要,但它是相对通用的。线程会被安排执行。当操作系统真正运行它时,是完全不可预测的。在今天普遍存在多核处理器的机器上,您有较大的几率获得“快速”响应。
thread::join()只是确保线程执行完成。您永远不会编写像这样的代码,启动一个线程然后等待它完成是完全没有意义的。最好直接执行线程的代码。同样的结果,而且不会拖累操作系统创建线程。

我现在明白了,调用join函数与同步版本是一样的。既然我更好地理解了这一点,我需要再考虑一下。谢谢。 - Chemistpp

1

你无法知道线程会以什么顺序或在哪个核心上运行。

一个POSIX线程基本上被Linux视为进程,与另一个进程共享堆。内核将安排它们并决定使用哪个核。例如,查看生成的(受保护的)汇编代码时,您不会看到任何“多核/多线程编程”,因为这是在内核级别完成的。

这就是为什么存在像join这样的函数和类似互斥体/信号量的工具来处理竞态条件和其他相关事项的原因。


1
当我调用join()方法时,线程正在执行还是在创建线程和调用join()之间的某个时间运行?
线程是在您创建线程后执行(启动),具体取决于可用资源,这可能需要一些时间。 join()方法等待(阻塞)直到线程完成其工作。
建议:我不会将变量命名为Sync对象This,这很令人困惑,因为存在关键字this。
在Async::Inc()中调用join()后,您可以安全地删除std::thread对象,而且您不需要将引用存储为成员变量,它仅在该函数中使用。
您还可以查看头文件,std :: atomic 或std :: atomic_int。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接