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 }