最近在看《C++并发编程实战》,虽然翻译略差,还是开篇记录一下。

启动线程

最简单的启动线程的方式:

void do_something();
std::thread my_thread(do_something());

std::thread可以传入任何callable的类型,所以你可以用一个重载了operator()的类的实例构造std::thread

class A
{
public:
    void operator()() const
    {
        std::cout << "operator()";
    }
};
A a;
std::thread my_thread(a);
my_thread.join();

这里输出operator()。然而看到std::thread my_thread(a),是不是感觉它很像函数声明?考虑当一个临时未命名的变量传递给std::thread,如

std::thread my_thread(A());

编译器会认为其声明了函数my_thread(),参数为一个指向返回类型为A的函数指针。下面的代码展示了该过程:

std::thread my_thread(A())
{
    std::cout << "Functioh new_thread";
    return std::thread();
}

A test()
{
    std::cout << "test\n";
    return *new A();
}
A (*p)();
p = test;
my_thread(p);

该例输出Functioh new_thread。为了避免这种情况,可以使用:

std::thread my_thread((A())); //添加额外的括号防止其杯解释为函数声明
std::thread my_thread{A()}; //统一初始化语法

当然也可以使用lambda:

std::thread my_thread([]{
    do_something();
});

等待线程完成

当你启动了一个线程,需要确保在std::thread对象被销毁前join()或者detach()。如果在线程启动后且join()有异常产生,join()的调用有可能被跳过。下面的代码提供了一个简单的解决方式:

struct func
{
    int& i;
    func(int& i_): i(i_){}
    void operator()()
    {
        for unsigned j = 0; j < 1000000; ++j)
        {
            std::cout << j << std::endl;
        }
    }
};

void f()
{
    int i = 0;
    func my_func(i);
    std::thread t(my_func);
    try
    {
        do_something_in_current_thread();
    }
    catch(...)
    {
        t.join();
        throw;
    }
    t.join();
}

该段代码在try/catch内外两次join(),十分冗余。RAII提供了更清晰简明的方法:

class thread_guard
{
    std::thread& t;
public:
    explicit thread_guard(std::thread& t_):
        t(t_)
    {}
    ~thread_guard()
    {
        if(t.joinable())
        {
            t.join();
        }
    }
    thread_guard(thread_guard const&) = delete;
    thread_guard& operator=(thread_guard const&) = delete;
};
struct func;

void f()
{
    int i = 0;
    func my_func(i);
    std::thread t(my_func);
    thread_guard g(t);
    
    do_something_in_current_thread();
}

当执行到f的末尾,thread_guardg会被销毁,其析构函数会等待t执行完成。thread_guard的拷贝和赋值构造均被禁用,为了防止出现拷贝或赋值该对象这种危险的动作。如果无需等待,可以直接detach该线程。

后台运行线程

std:thread::detach()将线程与当前线程分离,没有直接的方法与其通信,其所有权和控制权交给C++ Runtime。UNIX中,分离的进程被称作守护线程(daemon thread),通常它们是长时间运行的,执行一些后台任务。《C++并发编程实战》给了一个示例,比较简单不再讲解。