文章目录
一.发起线程
类成员函数作为线程函数
#include <iostream>
#include <thread>
class X
{
public:
void f(){
std::cout << "hello" << std::endl;
}
private:
};
void oops()
{
X my_x;
std::thread t(&X::f,&my_x);
t.join();
}
int main()
{
oops();
std::cin.get();
}
传入可调用对象作为std::thread构造函数的参数代码
#include <thread>
#include <iostream>
void do_something(int& i)
{
++i ;
}
struct func
{
int& i;
func(int& i_) :i(i_) {}
//func(func& j) = delete;
void operator()()
{
std::cout << "3.取新线程内部的可调用对象this指针:\n\t" << this << std::endl;
std::cout << "4.取新线程内部的可调用对象的成员变量i地址:\n\t" << &i << std::endl;
for(unsigned j=0;j<10000000;++j)
{
do_something(i);
}
}
};
void oops()
{
int some_local_state=0;
std::cout << "1.取oops()函数作用域内的局部变量some_local_state地址:\n\t" << &some_local_state << std::endl;
func my_func(some_local_state);
std::cout << "2.取oops()函数作用域内的局部变量my_func地址:\n\t" << &my_func << std::endl;
std::thread my_thread(my_func);
my_thread.detach();
// my_thread.join();
}
int main()
{
oops();
std::cin.get();
}
编译运行,输出信息如图:
上图红色序号2和3的地址数值不同(可调用对象的内存地址),说明这两处不是同一个对象。
解析:std::thread my_thread(my_func) 在构造实例时,在新线程内部实例化了一个新的func类型对象,这个新的对象拷贝了my_func对象的内容(调用的拷贝构造函数),而不是引用my_func本体。
上图红色序号1和4的地址数值相同,说明这两处是同一个对象
解析:my_func的内部成员int &i是对some_local_state的引用,新线程内部实例化了一个新的func类型对象,拷贝了my_func的内容,这个新的func类型对象的内部成员int &i也是some_local_state引用。
注意: my_thread.detach() 明确了不等待新线程运行结束。于是oops()退出后,新线程有可能还在继续运行,do_something(i)的下一步调用,会访问已经销毁的局部变量some_local_state。
二.等待线程完成
在std::thread对象销毁前,我们要确保已经调用了join()或detach()。通常,若要分离线程,在线程启动后调用detach()即可,这不成问题。然后,假使打算等待线程结束,则需要小心地选择执行代码的位置来调用join()。原因是,如果线程启动后有异常抛出,而join()尚未执行,则该join()调用会被略过。
在出现异常时等待,运用RAII机制
RAII,异常安全(exception-safe)C++ 标准保证了当异常发生时,会调用已创建对象的析构函数。
运用RAII机制确保新线程在函数f()退出前终结,在 ~thread_guard() 中调用 join() 时,先调用 joinable() 判断新线程能否汇合,因为在任何执行线程上 join() 只能被调用一次,如果线程已经汇合过,再次调用 join() 则是错误行为。
#include <thread>
class thread_guard
{
std::thread& t;
public:
explicit thread_guard(std::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 do_something(int& i)
{
++i;
}
struct func
{
int& i;
func(int& i_):i(i_){}
void operator()()
{
for(unsigned j=0;j<1000000;++j)
{
do_something(i);
}
}
};
void do_something_in_current_thread()
{
throw - 1;
}
void f()
{
int some_local_state;
func my_func(some_local_state);
std::thread t(my_func);
thread_guard g(t);
do_something_in_current_thread();
}
int main()
{
f();
}
三.在后台运行线程
调用std::thread对象的成员函数detach(),会令线程在后台运行,遂无法与之直接通信。假若线程被分离,就无法等待它完结,也不可能获得与它关联的std::thread 对象,因而无法汇合该线程。然后分离的线程确实仍在后台运行,其归属权和控制权都转移给了C++运行时库,由此保证,一旦线程退出,与之关联的资源都会被正确回收。
UNIX操作系统中,有些进程叫做守护进程,它们在后台运行且没有对外的用户界面;沿袭这一概念,分离出去的线程常常被成为守护线程。这种线程往往长时间运行。几乎在应用程序的整个生命周期内,它们一直运行,以执行后台任务,如文件系统监控,从对象缓存中清除无用数据项,优化数据结构等。
另一种模式,就是由分离线程执行"启动后即可自主完成"的任务;我们还能通过分离线程实现一套机制,用于确认线程完成运行。
注意只有joinable() 返回true时,才能调用detach() 分离线程。
四.向线程函数传递参数
错误的传参方式:
void f(int i, std::string const& s)
{
std::cout << "代码本意,字符串s的内容应是\t" << "123" << std::endl;
std::cout << "实际的能容是\t" << s << std::endl;
}
void oops()
{
char buffer[1024];
sprintf_s(buffer,1024,"%d", 123);
std::thread t(f,1,buffer);
t.detach();
}
int main()
{
oops();
std::cin.get();
}
编译运行,结果如图:
传入的内容是字符串"123",实际显示的是空白。
buffer是个局部数组指针,我们本来希望将buffer隐式转换成string对象,再将其作为函数参数,可惜转换未能及时发生,新线程的线程函数被调用时,oops()就已经退出了,buffer已经被销毁,继而引发了未定义行为。这是因为:std::thread的构造函数原样复制所提供的参数,并未令其转换成我们预期的参数类型。
参数传入std::thread的构造函数之前完成转换。
在buffer传入std::thread的构造函数之前,就把它转换成string对象。
#include <iostream>
#include <thread>
void f(int i, std::string const& s)
{
std::cout << "代码本意,字符串s的内容应是\t" << "123" << std::endl;
std::cout << "实际的内容是\t" << s << std::endl;
}
void oops()
{
char buffer[1024];
sprintf_s(buffer,1024,"%d", 123);
std::thread t(f,1,std::string(buffer));
t.detach();
}
int main()
{
oops();
std::cin.get();
}
运行结果如图:
线程函数的参数是非const引用,std::ref函数
需求:在新线程函数f()内部,改写oops()作用域内的对象,我们想要f()的参数是非const引用,将对象通过引用方式传入f(),这时候需要使用std::ref方法才能做到。
#include <iostream>
#include <thread>
void f(int i, std::string & s)
{
s = "ok";
}
void oops()
{
std:: string s;
std::thread t(f,1,std::ref(s));
t.join();
std::cout << "现在的string对象s的内容是:\t" << s << std::endl;
}
int main()
{
oops();
std::cin.get();
}
运行结果如图:
线程函数的参数只能移动不能复制,std::move函数
#include <iostream>
#include <thread>
#include <memory>
void f(std::unique_ptr<std::string> ptr)
{
std::cout << ptr->c_str() << std::endl;
}
void oops()
{
std::unique_ptr<std::string> ptr(new std::string("hello"));
std::thread t(f,std::move(ptr));
t.join();
}
int main()
{
oops();
std::cin.get();
}
五. 转移线程归属权
void f1();
void f2();
1.创建线程1,并使之与t1关联,代码如下:
std::thread t1(f1);
- t1将线程1的归属权移交给了t2,现在t2接管了线程1,t1内部不再关联任何线程。
代码如下
std::thread t2 = std::move(t1);
- 创建线程2,线程2与临时的std::thread对象关联在一起,临时的std::thread对象移交线程2的归属权给t1,现在t1与线程2关联,代码如下:
t1 = std::thread(f2);
- 构造一个std::thread对象,未关联任何执行线程
std::thread t3;
5.将t2相关联的线程1的归属权移交给t3,现在t2不再与任何线程相关联
t3 = std::move(t2);
错误:试图向正管控着一个线程的std::thread对象赋值
6.t1关联着线程2,却试图将t3关联的线程1归属权转移给t1,错误代码如下:
t1 = std::move(t3);
从函数内部返回std::thread对象
std::thread test()
{
void f();
return std::thread(f);
}
std::thread test()
{
void f();
std::thread t = std::thread(f);
return t;
}
向函数内部传入std::thread对象
void test(std::thread t);
void run()
{
void f();
test(std::thread t(f));
std::thread t(f);
test(std::move(t));
}
std::thread作为类成员
#include <thread>
#include <utility>
#include <iostream>
class scoped_thread
{
std::thread t;
public:
explicit scoped_thread(std::thread t_):
t(std::move(t_))
{
if(!t.joinable())
throw std::logic_error("No thread");
}
~scoped_thread()
{
t.join();
}
scoped_thread(scoped_thread const&)=delete;
scoped_thread& operator=(scoped_thread const&)=delete;
};
void do_something(int& i)
{
++i;
}
struct func
{
int& i;
func(int& i_):i(i_){}
void operator()()
{
for(unsigned j=0;j<1000000;++j)
{
do_something(i);
}
}
};
void do_something_in_current_thread()
{}
void f()
{
int some_local_state =0;
scoped_thread t(std::thread(func(some_local_state)));
do_something_in_current_thread();
}
int main()
{
f();
}
std::thread存放入容器内
#include <vector>
#include <thread>
#include <algorithm>
#include <functional>
void do_work(unsigned id)
{}
void f()
{
std::vector<std::thread> threads;
for(unsigned i=0;i<20;++i)
{
threads.push_back(std::thread(do_work,i));
}
std::for_each(threads.begin(),threads.end(),
std::mem_fn(&std::thread::join));
}
int main()
{
f();
}
六. 识别线程std::thread::id,支持完整的条件运算符
#include <iostream>
#include <thread>
#include <chrono>
void foo()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
int main()
{
std::thread t1(foo);
std::thread::id t1_id = t1.get_id();
std::thread t2(foo);
std::thread::id t2_id = t2.get_id();
std::cout << "t1's id: " << t1_id << '\n';
std::cout << "t2's id: " << t2_id << '\n';
t1.join();
t2.join();
}
七. 获取硬件支持的并发线程数
#include <iostream>
#include <thread>
int main() {
unsigned int n = std::thread::hardware_concurrency();
std::cout << n << " concurrent threads are supported.\n";
}