std::get<C++11多线程库>(02): 如何启动一个线程
1 #include <QCoreApplication> 2 #include <iostream> 3 #include <thread> //管理线程的类和函数 4 5 /* 6 * 话题1: 如何启动 7 * 8 * 在使用C++线程标准库时, 9 * C++如何启动一个线程, 变为了如何构造一个 std::thread 对象。 10 * 11 * 构造 std::thread 有以下几种: 12 * 1. 传入一个函数作为参数 13 * 2. 传入一个可调用对象 14 * 3. 传入一个 lamdba 对象 15 */ 16 17 /* 18 * 思考:对于一个线程, 如何给线程传入参数, 如何从线程得到返回值 19 */ 20 21 22 /* 23 * 话题2:启动线程之后,就需要明确是等待线程的执行(加入式),还是让其自行运行(分离式)。 24 * 25 * 如果std::thread对象销毁之前还没有做出决定,新线程就会终止。需要确保线程能够正确的加入(joined)或分离(detached)。 26 * 因此,如果未明确新线程的执行方式,则可能导致新线程运行不完整。 27 * 28 * std::thread 的析构函数会调用 std::terminate() 终止新线程的运行。 29 * 30 */ 31 32 void way_one(){//话题1 33 std::cout<<"way_one hello word"<<std::endl; 34 } 35 36 class way_two//话题1 37 { 38 public: 39 way_two(){} 40 void operator()(){ 41 std::cout<<"way_two hello word"<<std::endl; 42 } 43 }; 44 45 void way_threed(){//话题1 46 std::cout<<"way_threed hello word"<<std::endl; 47 } 48 49 void way_four(){//话题2 50 std::cout<<"way_four hello word"<<std::endl; 51 } 52 53 54 //int main(int argc, char *argv[]) 55 //{ 56 // QCoreApplication a(argc, argv); 57 58 // std::thread t_one(way_one); 59 // t_one.join(); 60 61 // way_two _way_two; 62 // std::thread t_two(_way_two); //函数对象 _way_two 会复制到新线程的内存空间中, 函数对象的调用和执行都在新线程的内存空间中。 63 // //函数对象的副本应与原始函数对象保持一致,否则得到的结果会与我们的期望不同。 64 // t_two.join(); 65 66 //// std::thread _way_two_1(way_two()); //编程了定义一个函数 std::thread func ( param ); 67 // //有件事需要注意,当把函数对象传入到线程构造函数中时,需要避免“最令人头痛的语法解析”(C++’s most vexing parse, 68 // //中文简介)。如果你传递了一个临时变量,而不是一个命名的变量;C++编译器会将其解析为函数声明,而不是类型对象的定义。 69 70 // std::thread _way_two_2((way_two())); //解决办法1: 给临时对象添加一对儿括号 71 // _way_two_2.join(); 72 73 // std::thread _way_two_3{way_two()}; //解决方法2: 采用 大括号的初始化语义 74 // _way_two_3.join(); 75 76 // std::thread _way_three([]{ 77 // way_threed(); 78 // }); 79 // _way_three.join(); 80 81 // std::thread _way_four(way_four); //可能会执行到,也可能执行不到,看运气 82 // //main()函数执行结束, _way_four 对象会被析构, std::thread 析构会调用 std::terminate() 终止新线程。 83 // return a.exec(); 84 //} 85 86 /* 87 * join 函数: 初始线程等待新县城执行完毕再继续往下执行。 88 * 89 * 把上面的所有join的调用放到 main() 函数的 return a.exec() 的上方,再来看看。 90 * 91 * 2.1.2 等待线程完成 92 * 每创建一个 std::thread 对象,就紧接着使用 join(),因此原始线程在其生命周期中并没有做什么事, 93 * 使得用一个独立的线程去执行函数变得收益甚微, 94 * 但在实际编程中,原始线程要么有自己的工作要做;要么会启动多个子线程来做一些有用的工作,并等待这些线程结束。 95 * 所以说,上面的 main()函数的流程,很明显不恰当,应当像下面的 main() 一样,在最后使用 join(), 96 * 在类中使用 std::thread 可以在对应的析构函数中使用 join()。 97 * 98 * join()是简单粗暴的等待线程完成或不等待。 99 * 当你需要对等待中的线程有更灵活的控制时,比如,看一下某个线程是否结束, 100 * 或者只等待一段时间(超过时间就判定为超时)。想要做到这些,你需要使用其他机制来完成,比如条件变量和期待(futures) 101 * 102 * 103 * 调用join()的行为,还清理了线程相关的存储部分,这样std::thread对象将不再与已经完成的线程有任何关联。 104 * 这意味着,只能对一个线程使用一次join();一旦已经使用过join(),std::thread对象就不能再次加入了, 105 * 当对其使用joinable()时,将返回false。 106 */ 107 int main(int argc, char *argv[]) 108 { 109 QCoreApplication a(argc, argv); 110 111 std::thread t_one(way_one); 112 113 way_two _way_two; 114 std::thread t_two(_way_two); //函数对象 _way_two 会复制到新线程的内存空间中, 函数对象的调用和执行都在新线程的内存空间中。 115 //函数对象的副本应与原始函数对象保持一致,否则得到的结果会与我们的期望不同。 116 117 // std::thread _way_two_1(way_two()); //编程了定义一个函数 std::thread func ( param ); 118 //有件事需要注意,当把函数对象传入到线程构造函数中时,需要避免“最令人头痛的语法解析”(C++’s most vexing parse, 119 //中文简介)。如果你传递了一个临时变量,而不是一个命名的变量;C++编译器会将其解析为函数声明,而不是类型对象的定义。 120 121 std::thread _way_two_2((way_two())); //解决办法1: 给临时对象添加一对儿括号 122 123 std::thread _way_two_3{way_two()}; //解决方法2: 采用 大括号的初始化语义 124 125 std::thread _way_three([]{ 126 way_threed(); 127 }); 128 129 130 t_one.join(); 131 t_two.join(); 132 _way_two_2.join(); 133 _way_two_3.join(); 134 _way_three.join(); 135 return a.exec(); 136 }