本章的讲解是围绕thread的接口进行的,下面是thread的接口定义:

 

class thread{
public:
    class id;    //声明
    //construction an destruction
    //**一、构造
    thread()    noexcept;
    ~thread();
    template<typename Callable, typename Args...>
    explicit thread(Callable&& func, Args&&...args);
    //copy and move
    //**二、拷贝、赋值
    thread(const thread& other) = delete;
    thread(thread&& other) noexcept;

    thread& operator=(const thread& other) = delete;
    thread& operator=(thread&& other) noexcept;
    //一个好的类一定实现一个异常安全的swap(effective c++)
    void swap(thread& other) noexcept;
    //**三、join、detach
    void join();
    void detach();
    bool joinable() const noexcept;
    //**四、class id
    id get_id() const noexcept;
    native_handle_type native_handle();
    //**五、
    static unsigned hardware_concurrency() noexcept;
}

 

一、构造:

暂时忽略默认构造函数,只剩下一个构造函数了,这是一个可变模板参数的构造函数,并且使用了完美转发。下面针对参数讨论三点

1Callable函数对象(callable object,标准库这样叫的),有四种:functionmember functionfunction objectlambda,具体使用如下代码所示:

 

 

#include<thread>
#include<iostream>

class BackgroundClass{
public:
    void operator()()const{
        std::cout << "function object" << std::endl;
    }
    void ClassMem()const{ std::cout << "member function" << std::endl; }
};
void Func(){ std::cout << "function"<<std::endl; }
int main(){
    //1
    //std::thread myThread(BackgroundClass()); 这是构建一个函数myThread,返回thread,参数是一个临时对象
    //采用加括号std::thread myThread((BackgroundClass()))
//或利用统一初始化std::thread myThread{BackgroundClass()};
    std::thread myThread((BackgroundClass()));
    //2
    //std::thread myThread(&BackgroundClass::ClassMem,BackgroundClass());
    //3
    //std::thread myThread([](){std::cout << "lambda" << std::endl; });
    //4
    //std::thread myThread(Func);
    myThread.join();
    return 0;
}

2、给callable object 传递参数:

  正如可变参数模板使用一样,只要将需要的参数按顺序写到callable object后面即可。

  需要注意的是,thread会将传递给他的参数原照原拷贝下来(实参是指针就拷贝指针;实参是对象就拷贝对象,即使形参是引用),然后thread在函数调用时候,利用这些拷贝下来的参数去调用。

 

int main(){
    std::string str(“jia”);
    std::thread myThread([](std::string&str){
        str += ”feng”;
    }, str);
    myThread.join();
    std::cout << str << std::endl;    //print:jia
    return 0;
}

lambda形参为引用就是为了改变str,但是thread会把str拷贝到临时对象,然后用这个临时对象去调用这个lambdalambda引用的是这个临时对象,当线程结束时候,临时对象销毁,并没有达到修改str的目的。要修改必须要让thread知道你传递是的”引用”,这可以利用std::ref(str)做到。

td::thread myThread([](std::string&str){
    str += ”feng”;
}, std::ref(str));
//print::jiafeng

如果使用指针是不会出现这个问题的,因为同一个进程同一地址空间。

std::thread myThread([](string* pstr){
    (*pstr) += ”feng”;
}, &str);
//print:jiafeng

注意智能指针unique_ptr,他们有拷贝构造只有移动,因此只能用移动构造传参:

void ProcessBigObject(std::unique_ptr<big_object>);

std::unique_ptr<big_object> p(new big_object);

std::thread t(ProcessBigObject,std::move(p));

这里首先将big_object moveinternal storage然后再move到函数调用。

 

3、thread对象创建效果:使用默认构造函数只是创建一个标记,并未有任何实质线程,get_id()返回默认结果,joinable()返回false;利用函数对象创建线程对象,线程会立刻加载执行,如果未能成功加载就会抛出异常。

 

二、拷贝、赋值:

  threadunique_ptrIO一样,没有拷贝构造、赋值,只有移动构造,移动赋值,这注定了threadunique_ptr相似,里面的真正线程只能在thread对象之间转移,不能出现两个thread对象内部是同一个线程,就像两个unique_ptr不能指向同一个对象一样。

1、拥有权转移:

 

void some_func();
void som_other_func();
std::thread t1(some_func);         //t1拥有some_func
std::thread t2(some_other_func);    //t2拥有some_other_func
t1=std::thread(some_other_func);  //程序崩溃解释如下

 

  临时对象激活t1的移动构造函数,在得到some_other_func之前,t1会先调用自己的析构函数,由于t1没有调用detachjoin(后面详细介绍),因此析构函数会调用std::terminate程序崩溃

 

std::thread t3;                    //t3什么都没有
t3=std::thread(some_func);        //激活t3的移动构造,t3此时拥有some_func
std::thread t4;
t4=std::move(t3);                //将t3的some_func转交给t4

 

2thread对象作为函数调用参数、返回对象:

 

void func1(std::thread);
func1(std::move(t1));
func1(std::thread(some_func));
std::thread g(){
    //1 return std::thread(some_func);
    std::thread t(some_func);
    return t;
}

 

三、joindetach:

 

  当一个thread对象构建完后、生命有效期内必须调用joindetach,如果不这样做,thread对象的生命期结束,调用析构函数时会调用std::terminate(),致使应用程序over

 

  当你不需要等待一个线程的执行结果时,在构建完thread对象后直接调用detach即可,往后拖延一方面有异常危险,一方面是资源的浪费。

 

  当你需要等待一个线程的执行结果时,必须使用join。使用join的麻烦在于“将join放在什么位置?”,如果太靠前,主线程阻塞等待辅助线程结束,起不到并行的目的,如果放的太靠后,从thread对象构建到join的调用中间代码很多,很多代码意味着出现异常的可能性很大。join是必须调用的,即使中间代码抛出异常,否则即使你处理了中间代码抛出的异常,应用程序照样over,因为join不调用辅助线程会over整个应用程序。

 

  启发于RAII,使用对象管理资源(effective c++),创建一个对象管理threadjoin

 

class ScopedThread{
    std::thread m_thread;
public:
    //线程只能移动,传参要用std::move()或临时对象
    explicit ScopedThread(std::thread t) :m_thread(std::move(t)){
        if (!m_thread.joinable()){
            //传递进来的线程是空的
            throw std::logic_error(“no thread have constructed!”)
        }
    }
    ~ScopedThread(){
        m_thread.join();
    }
    //禁止拷贝、赋值
    ScopedThread(const ScopedThread&) = delete;
    ScopedThread& operator=(const ScopedThread&) = delete;
}
//使用:
int main(){
    //不管后面是否抛出异常,都能保证thread::join调用
    ScopedThread(std::thread(some_func));
    some_func_with_exception();
    return 0;
}

 

四、class id

线程的唯一标识类,定义了各种比较操作符,因此可以用作关联容器中的索引。所有空线程id一样,如果两个id相等要么指向同一个线程,要么指向的两个线程实际都是空的。std::cout<<id<<std::endl;输入结果依赖于平台,因为标准只规定了两个不一样的线程输出结果不一样就行了,没有太多。

五、hardware_concurrency:

  如果调用成功就返回当前应用程序可以支持的线程数,否则返回0。这个函数不是一定调用成功的,因此调用后要进行判断。