class background_task
{
public:
void operator()() const
{
do_something();
do_something_else();
}
};
background_task f;
std::thread my_thread(f);
//这里的疑惑是为什么f是调用oprator()(),我知道他重载了(),那正常不应该是代入()
//std::thread my_thread(f())Error;
std::thread my_thread((background_task ()));//Correct
std::thread my_thread{background_task()};//Correct 初始化列表
//std::thread my_thread(background_task() ); Error
//参数带入了临时变量,编译器误以为是为声明函数
//返回值为std::thread 函数名为 my_thread 参数为background **这里注意写法**
**这里需要注意的是background_task f 传入的实参和形参是不一样的地址 **
#include#include class func { public: int& i; func(int& i_) :i(i_) {}; void operator()() { for (unsigned j = 0; j < 100000; ++j) { do_something(i+j); } } void do_something(int i) { std::cout << i << std::endl; }; }; void oops() { int some_local_state = 0; func my_func(some_local_state); std::thread my_thread(my_func); my_thread.detach(); //以上代码段注意一下`detach` 和join的区别 //detach : 不等线程内代码执行继续往下运行。 //join: 等线程内代码运行完再继续运行。 主要这里区别。 //上图用到了deatch ,即表明了,等oops函数结束后。新线程 my_thread线程可能还没起来。 //主要点是这里,毕竟新县城用到oops内的局部变量,涉及到了生命周期的问题。 } int main() { oops(); return 1; }
在2.1.3中有讲叙到用try/catch 来进行一个异常保护的机制。但是此机制显得有点冗余。有点不太优雅。如下图
struct func;
void f{
int some_local_state=0;
func my_func(some_local_state);
std::thread t(my_func);
try{ //此处加了一个try保护,防止在当前线程的时候出现异常。然后t线程没join执行。
do_something_in_current_thread();
}
catch(...){
t.join();
throw;
}
t.join();
}
还有另外一种较为优雅点的方法就是使用RAII机制,即类资源回收。运用析构来进行join。
class thread_gurad
{
std::thread& t;
public:
explicit thread_guard(std::thread& _t):t(_t){};
~thread_guard(){
if(t.joinable()) //判断此函数是否能join 因为一个线程只能进行一次合并(join)
t.join;
}
thread_guard(thread_guard const&)=delete;
thread_guard& operator=(thread_guard const&)=delete;
}
struct func;
void f(){
int some_local_state=0;
func my_func(some_local_state);
std::thread t(my_func); //传入线程函数然后供线程进行回调。
thread_guard g(t); //创建一个RAII 的线程类,进行管理,当此f函数走完的时候。g类析构自动调用join
do_something_in_current_thread(); //执行当前所在线程需要操作的事情。
}
此方法即便do_something_in_current_thread 抛出异常,函数f()退出,以上行为仍会发生。
这里有个细节性的需注意一下,例下图:
void f(int i,std::string const& s); //这里的线程回调函数需要的是一个string 类型
void oops(int some_param)
{
char buffer[1024]; // 1
sprintf(buffer, "%i",some_param);
std::thread t(f,3,buffer); // 2 //这里传入的是一个指针,指向一个局部变量,很可能会出现
//当oops函数运行完成时,buff指向的局部变量会销毁,从而导致一些未定义的行为。
t.detach();
}
向线程函数传递参数的错误细节
void f(int i, std::string const& s) {
cout << s << endl;
};
void oops(int some_param)
{
char buffer[1024]; // 1
sprintf_s(buffer, "%i", some_param);
std::thread t(f, 3, string(buffer)); // 2
std::thread t(f, 3,buffer); // 2 ERROR 这里需要注意一个错误点就是这里传入了一个指针,然后oopes 结束的时候地址内的信息已经回收
// 线程回调f函数的时候已经拷贝不到buff指针内的值了,所以这个时候程序能运行但是并不能得到想要的结果。
//我们需要先提前转换成string(buffer);
t.detach();
}
int main() {
oops(3);
system("pause");
return 1;
}}
关于std::ref ,主要讲述的是在某些场景, 代入线程函数的时候线程需盲目的拷贝下列 int&a
但是const int&a 不允许拷贝,这个时候就需要用到std::ref了,这个就是将数值转换成引用类型
引用文中一句话:
#include#include #include #include using namespace std; void fun(const int& a) { cout << std::hex << &a << "----" << a << endl; } int main() { int he = 10; cout << std::hex << &he << "----" << he << endl; thread t(fun, std::ref(he)); //!!!这里std:ref 的作用是转换成一个引用类型,而且还有一层作用类似于文本的剪切功能, //用于一些不允许拷贝的变量 //thread t(fun, he); ERROR 因为fun函数期待引入一个引用类型,但是此时传入的是非常量引用。 t.detach(); system("pause"); return 1; }
如何传递参数为成员函数到线程中以及std::move 讲解也称作 移交线程归属权
#include#include #include #include using namespace std; class X { public: void do_lengthy_work(int argc) { cout << argc << endl; }; }; int main() { X my_x; int num(10); std::thread t(&X::do_lengthy_work, &my_x, num); t.detach(); std::shared_ptr m_ptr(new X); std::thread w(&X::do_lengthy_work, m_ptr, num); //如若遇上不允许拷贝只可以移动的参数时候,例unique_ptr 这个时候就需要用move移动这个变数了,比较像在文本编辑的"剪切"操 //使用"移动"转移原对象后,就会留下一个空指针(NULL)。即为本指针unique_ptr为nullptr, w.detach(); system("pause"); return 1; }
个人小结:
这一章中主要是对于thread 的启动,等待的介绍。
最基础的数据保存方式:互斥量
C++中通过实例化 std::mutex 创建互斥量实例,通过成员函数lock()对互斥量上锁,unlock() 进行解锁。。C++标准库为互斥量提供了一个RAII语法 的模板类 std::lock_guard



