传递参数为线程函数

如果线程函数包含参数,即使指定参数类型为引用,参数依然会以复制的方式传递过去。考虑函数线程接受一个std::string参数:

void f(int i, const std::string& str);
std::thread t(f, 3, "hello");

字符串常量值char* const类型会在新线程中被转换成std:string类型。而当一个局部变量的指针传递给线程函数时:

void f(int i, const std::string& str);
void oops(int length)
{
    char buffer[1024];
    sprintf(buffer, "%i", length);
    std::thread t(f, 3, buffer);
    t.detach();
}

函数oops可能会在参数const std::thread& str构造完成前退出,悬空指针可能会造成未定义行为,因此需要将其在线程构造时转成std::string,如下:

std::thread t(f, 3, std::string(buffer));

产生这种risk的根本原因就是就是因为std::thread的构造函数对传入的参数进行了复制,而不是使用其引用。解决方案是使用std::ref包装需要被引用的参数,如下:

void update_data(Data& data);
void main()
{
    Data data;
    std::thread t(update_data, std::ref(data));
    t.join();
    process(data);
}

std::refstd::thread一样同时在C++11中被引入,主要用于函数式编程。std::thread的构造函数和std::bind的机制类似,所以你可以使用一个成员函数作为线程函数,如下:

class Test
{
public:
    void do();
}
Test test;
std::thread t(&Test::do, &test);

新线程会调用test.do(),如果do()需要接受参数,在std::thread构造函数添加一个参数即可。
再考虑另一种情况:如果std::thread接受的参数不能被复制,只能被移动呢?比如其接受一个std::unique_ptr类型的参数,只有一个std::unique_ptr的实例可以指向某对象,在多个std::unique_ptr之间转移对象的使用权会导致原来的拥有者成为空指针。这个时候需要使用std::move将对象的所有权转移到新的线程中,如下:

void process(std::unique_ptr<Data>);
std::unique_ptr<Data> p_d(new Data);
p_d->prepare_data(42);
std::thread t(process, std::move(p_d));

转移线程所有权

std::thread支持移动构造,一个线程的所有权可以在不同的std::thread实例中转移。

void f1();
void f2();
std::thread t1(f1);
std::thread t2 = std::move(t1);
t1 = std::thread(f2);
std::thread t3;
t3 = std::move(t2);
t1 = std::move(t3);               //t1已经拥有了一个运行实例,该线程会被std::terminate()

也可以让函数返回一个std::thread

std::thread f()
{
    void some_function();
    return std::thread(some_function);
}
std::thread g()
{
    void some_other_function(int);
    std::thread tt(some_other_function, 42);
    return t;
}

如果要将线程所有权转移到函数中:

void f(std::thread t);
void g()
{
    void some_function();
    f(std::thread(some_function);
    std::thread t(some_function);
    f(std::move(t));
}

std::thread支持移动语义的好处之一,是在上文thread_guard类的基础上,可以直接获取线程的所有权,而不仅仅是其引用。因此可以构造一个scoped_thread类,将线程的分离或结合全交给这个类。

class scoped_thread
{
    std::thread t;
public:
    explicit scoped_thread(std::thread t_):
        t(std::move(t_))
    {
        if(!t.joinable())
            throw std::logic_error("Not joinable thread!");
    }

    ~scoped_thread()
    {
        t.join();
    }
    
    scoped_thread(scoped_thread const&) = delete;
    scoped_thread& operator=(scoped_thread const&) = delete;
}

struct func;

void f
{
    int i;
    scoped_thread t(std::thread(func(i)));
    
    ...... // do something
}

当程序运行到f的结尾,scoped_thread会被析构,而其析构函数不必再check线程是否joinable,因为在转移所有权时已经检查过。