资料整合来源书籍: c++并发编程实战
一: 运行线程
方法1: 传入函数
方法2:用函数对象(重载了圆括号运算符)
方法3:用lambda表达式
//输出语句若用cout<< *** <<endl可能会乱序
void func()
{
cout<< "int func()\n";
}
class A
{
public:
void operator()()const
{
cout<< "int class A\n" ;
}
};
int main()
{
auto l=[]{cout<< "in lambda\n";};
A a;
//定义函数调用
thread t1(func);
//定义类来调用
//1.通过实例对象
thread t2_0(a);
//2,3
//注意不能 thread t(A()), 这样会解释成函数声明,函数名是t,接收一个参数,返回thread
thread t2_1((A()));
thread t2_2{A()};
//定义lambda调用
thread t3(l);
t1.join();t2_0.join();t2_1.join();t2_2.join();
}
运行结果
方法4:成员函数指针+对象双参数传递的方式
class A
{
public:
void func()
{
cout<< "in A::func()" <<endl;
}
};
int main()
{
A a;
//第一个参数为成员函数指针, 第二个参数为执行对象, 即线程执行a.func()函数
thread t1(&A::func,&a);
t1.join();
}
二: 线程等待
int main()
{
int i = 1;
A a(i);
thread t1(a);
t1.join();
//thread 在销毁前必须调用二者之一
//t.join() 主线程阻塞直至子线程结束
//或t.detach() 交给运行库管理,主线程继续往下走
}
若调用detach()时, 可能出现的问题:
子线程引用主线程的数据, 而主线程结束后销毁了数据, 子线程还在运行, 就会造成悬空访问
示例:
class A
{
int &i;
public:
A(int &i_) : i(i_)
{}
void operator()() const
{
int j;
for (j = 0; j < 100000; j++)
{
printf("i:%d\n", i);
//子线程的i是引用, 主线程i销毁后, 会造成悬空引用
}
}
};
int main()
{
int i = 1;
A a(i);
thread t1(a);
t1.detach();
//此时主线程结束, 销毁i, 子线程访问i出错
}
-----------------------------
在线程销毁前必须调用join或detach, 否则出错(下面只示例join),
于是在出现异常的情况下须保证所有出口都要调用join(), 代码显得冗余...
void f()
{
int i = 1;
A a(i);
thread t1(a);
try
{
//...
}
catch (...)
{
t1.join();
throw ;
}
t1.join()
}
理想方案: 可以利用析构函数实现线程销毁前自动调用join()
class thread_guard
{
thread &t;
public:
explicit thread_guard(thread &t_) : t(t_)
{}
~thread_guard()
{
if (t.joinable())
{
t.join();
}
}
thread_guard(thread_guard const &) = delete;
thread_guard &operator=(thread_guard const &) = delete;
};
void f()
{
thread t1(func);
thread_guard guard(t1);
//即使不主动调用t1.join, 也会在f结束时, 在guard的析构函数里调用
}