一,启动线程
1. 使用C++线程库启动线程,即为构造std::thread对象:
void do_some_work();
std::thread my_thread( do_some_work );
std::thread构造方法
class A
{
public:
void operator()() const
{
std::cout << "hello" << std::endl;
}
};
A a;
std::thread my_thread(a);
std::thread my_thread(a);
调用函数对象a() 传入thread中 ,并把函数对象a 复制到新线程的存储空间中,函数对象的执行和调用都在线程的内存空间中进行。
注意:
-
如果你传递给thread是一个临时变量,而不是一个已经声明的变量时,会出现C++最令人头痛的语法解析(C++’s most vexing parse),C++编译器解析出的结果将会与你所想的出入很大:
std::thread my_thread( A() );//A()是一个临时的函数对象
这段代码被解析为:
这是一个函数声明,函数名为my_thread
。它可以接收一个没有参数的,并且会返回对象A的函数指针。my_thread函数的返回值是std::thread
类型。
怎么解决?
使用多组括号,初始化语法 或者 前面代码 都可以避免这个问题:
std::thread my_thread( (A()) );
std::thread my_thread{ A() };
-
如果不等待线程,必须保证线程结束之前,数据的有效性
struct F
{
int &i;
func(int &i_) : i( i _ ) { }
void operator() ()
{
for(unsigned j = 0; j < 100000; ++j)
{
std::cout << i << std::endl;
}
}
}
void oop()
{
int number = 0;
F my_f(unmber);
std::thread my_f(my_f);
my_f.detach();
}
由于不等待线程结束, 当oop()执行完成时, 新线程中的函数可能还在运行。 如果线程还在运行, 它就会去调用do_something(i)函数,这时,引用对象 i 已经被销毁了。
二,等待线程完成
如果需要等待线程, std::thread 实例可以使用join()。
class func1{//仿函数对于多线程的运用
public:
int &a;
explicit func1(int &a_) : a(a_) {}
int operator()(int c)
{
for(;a < c; ++a)
{
cout << a << endl;
}
cout << a << endl;
return a;
}
};
int main()
{
int st = 6;
func1 f(st);
int c = 10;
std::thread my_thread (f, c);
my_thread.join();
return 0;
}
三,特殊情况下的等待
当倾向在无异常的情况下使用join()时, 需要在异常处理过程中调用join(), 从而避免生命周期的问题。
- 可以通过使用“资源获取即初始化方式”(RAII, Resource Acquisition Is Initialization)解决此问题。它提供一个类, 在析构函数中使用join():
struct F
{
int &i;
func(int &i_) : i( i _ ) { }
void operator() ()
{
for(unsigned j = 0; j < 100000; ++j)
{
std::cout << i << std::endl;
}
}
}
class G
{
std::thread& t;
public:
explicit G(std::thread& t_) : t(t_) {}
~G()
{
if(t.joinable())
{
t.join();
}
}
G( G const& )=delete;//不让编译器自动生成,因为这可能会弄丢已经加入的线程。
G& operator= ( G const& )=delete;
};
struct F;
void oop()
{
int data = 0;
F struct_my_f(data);
std::thread my_t(struct_my_f);
G my_g(t);
do_something_in_current_thread();
}
函数oop() 执行完,对象my_g 也会被销毁, 即使do_something_in_current_thread() 抛出一个异常, 这个销毁依旧会发生。
四,后台运行线程
如果不想等待线程结束, 可以分离(detaching)线程, 从而避免异常安全(exception-safety)问题。 由于使用detach()会让线程在后台运行,如果线程分离, 那么就不可能有 std::thread 对象能引用它, 分离线程的确在后台运行, 所以分离线程不能被加入。
一个可结合的线程能够被其他线程收回其资源和杀死;在被其他线程回收之前,它的存储器资源(如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。
注意:
当在 std::thread 对象中分离线程时,不能对没有执行线程的 std::thread 对象使用detach()。因为和 join()的使用条件相同, 所以可以用同样的方式进行检查,当 std::thread 对象使用 t.detach() 之前,先使用t.joinable()检测该线程是否为可执行线程,如果返回的是true,就可以使用了。