启动线程
thread类的构造函数接收一个可调用的类型。可调用的类型,除了函数,还可以是函数对象和lamda表达式。
class foo{
public:
void operator() (){
for(int i = 0 ; i < 5; i++)
std::cout << "foo concurrent \n";
}
};
void fun(){
for(int i = 0 ; i < 5; i++)
std::cout << "lamda concurrent \n";
}
int main()
{
using std::thread;
foo my_foo;
thread t1(my_foo); //函数对象
thread t2(fun); //函数
thread t3([]{fun();});//lamda表达式
t1.join();
t2.join();
t3.join();
}
注意:在传入函数对象时,不能直接传入临时变量,这会引起c++语法解析错误(c++'s most vexing parse):
thread t2(foo());
foo()返回一个临时变量。对于上述代码,编译器会解析为返回thread类名为t2的函数,而不是创建一个线程。在传入函数对象时,我们除了创建一个非临时变量(如同最上方代码那样),还可以使用如下两种方式:
thread t4((foo()));
thread t5{foo()};
注意,t4的foo()外层还有一层()。
使用join()函数阻塞当前线程
当我们在当前线程中,创建一个新的线程。父线程结束时,会回收自己的资源,但并不会理会子线程是否结束,并强行调用线程的析构函数,此时会报terminate called without an active exception。
class foo{
public:
void operator() (){
for(int i = 0 ; i < 100000; i++)
std::cout << "foo concurrent \n";
}
};
int main()
{
std::thread t{foo()};
}
输出结果为:
foo concurrent
terminate called without an active exception
foo concurrent
foo concurrent
foo concurrent
foo concurrent
foo concurrent
foo concurrent
我们可以看到报了异常,且子线程并没有和我们想的一样输出100000个字符串,而是在中途就停止了。此时,我们可以使用join函数来让当前线程等待子线程。
join()函数会阻塞当前线程,只有当子线程结束之后,当前线程才会继续往下运行。joinable()函数会判断当前线程是否可以被等待。同一个线程只能被等待一次。
int main()
{
std::thread t{foo()};
if(t.joinable())
t.join();
}
异常情况下完成线程等待
int main()
{
std::thread t{foo()};
do_something();
t.join();
}
假设do_something()会抛出异常。当异常发生时,程序会停止,不再运行下方的代码,t线程也会被终止。为了避免线程被异常终止,我们需要在当前线程退出之前调用join函数。下面介绍2种方法完成异常情况下的线程等待。
1、使用try{}catch{}块
int main()
{
std::thread t{foo()};
try{
do_something();
}catch(...){
t.join();
throw;
}
t.join();
}
通过捕获异常的方法,在发生异常之后调用join()函数。
2、资源获取即初始化(RAII,Resource Acquisition Is Initialization)
将线程变量交由一个类来管理,并在类的析构函数中加入join。当发生异常或者线程正常退出时,管理类的对象就会被销毁,调用析构函数来触发join()函数。
class thread_guard
{
thread &t;
public :
explicit thread_guard(thread& _t) :
t(_t){}
~thread_guard()
{
if (t.joinable())
t.join();
}
thread_guard(const thread_guard&) = delete;
thread_guard& operator=(const thread_guard&) = delete;
};
int main()
{
std::thread t{foo()};
thread_guard(t);
do_something();
}
使用detach()函数完成线程分离
有些时候我们希望新建的线程和当前线程脱离关系。
detach()函数会将线程分离,当前线程的状态变化,并不会影响新建线程。
class foo{
public:
void operator() (){
for(int i = 0 ; i < 100000; i++)
std::cout << "foo concurrent \n";
}
};
int main()
{
std::thread t{foo()};
t.detach();
}
通过使用detach()函数,我们可以在当前线程结束之后,依旧运行新建线程,两者互不干扰。
但是,下面这种情况一定要注意!
class fun
{
public:
int& m_i;
fun(int& i) : m_i(i) {}
void operator() ()
{
for (unsigned j=0 ; j<1000000 ; ++j)
{
std::cout << m_i;
}
}
};
int main()
{
int i = 0;
std::thread t{fun(i)};
t.detach();
}
fun把i的引用作为变量保存。当main结束时,i的内存会被回收,但是此时新建线程仍然在运行,此时访问的i的地址的值是不确定的值,所以上述代码的运行结果是不确定的。所以,如果线程有调用引用或指针,要使用join(),避免使用detach()分离线程,除非可以保证子线程可以比父线程更早完成。
向线程函数传递参数
传递参数,只需要在初始化thread时,接在可调用类型之后。
void foo(int i){ std::cout << i}
int main(){
thread t(foo,1);
t.join();
}
但是要注意,默认的传值方式是值拷贝,thread会用新的地址来接收变量的值,即使函数接受的参数是引用也无效。
void swap(int & x, int & y){
int temp = x;
x = y;
y = temp;
}
int main(){
int x = 0;
int y = 1;
thread t(swap, x, y);
t.join();
cout << "x " << x << ",y " << y;
}
结果的x,y值并没有交还。我们需要使用std::ref()函数包裹x和y值才可以实现引用传递。
std::thread t(swap, std::ref(x), std::ref(y));
注:我的编译器上如果不加std::ref(),是无法通过编译的,会提示需要传入引用。我尝试了一下,当声明swap(int &, int &)时,thread初始化时,必须使用ref()传入参数,即必须使用std::thread t(swap, std::ref(x), std::ref(y)),否则无法通过编译。这是新版标准吗?还希望有大神解答。
使用类的成员函数作为线程函数
class f1{
public:
void hello(int i){
std::cout << i << std::endl;
}
};
int main(){
f1 f;
std::thread t(&f1::hello, &f, 1);
t.join();
}
第一个参数为成员函数,第二参数为对象,之后参数为成员函数的参数。
转移线程所有权和线程id
thread类支持移动语义,我们可以将一个右值赋值给一个thread变量,从而实现线程所有权的转移。但是,thread禁止了左值的赋值操作。将一个左值赋值给thread对象是违法的。我们可以通过get_id函数来查看进程的id号。
class f1{
public:
void hello(int i){
}
};
int main(){
f1 f;
std::thread t(&f1::hello, &f, 1);
std::cout << t.get_id() << std::endl;
std::thread t1 = std::move(t);
std::cout << t1.get_id();
//std::thread t2 = t1; //左值赋值违法!
//t.join(); //t没有控制权,操作违法!
t1.join();
}
程序输出为:
140189120116480
140189120116480
我们可以看出同一个线程在多个对象之间进行了传递。同时,所有权转移之后,t对象没有指向任何线程,此时调用join和detach将是违法的。
获取系统能并行的线程数量
使用std::thread::hardware_concurrency()函数可以获取系统能同时并发在一个程序中的线程数量。通过此数值,我们可以设置启动时的最佳线程数。