3.8 如何确保创建的线程一定运行起来?
在本章开头的小节里面我们介绍了如何创建线程,但是很少有人会注意到创建的线程如何确保一定运行起来了?很多人会说,对于使用系统 API 创建的线程,只需要判断一下创建的线程函数是否是调用成功的,这只做了一步,线程函数调用成功,也没法百分百保证线程函数一定运行起来了。
在一些“古老”或者“严谨”的项目中,你会发现这些代码创建线程时不仅判断线程创建函数是否调用成功,还会在线程函数中利用上文介绍的一些线程同步对象来通知线程的创建者线程是否创建成功。我们来看一段这样的代码:
#include <thread>
#include <mutex>
#include <condition_variable>
#include <iostream>
std::mutex mymutex;
std::condition_variable mycv;
bool success = false;
void thread_func()
{
{
std::unique_lock<std::mutex> lock(mymutex);
success = true;
mycv.notify_all();
}
//实际的线程执行的工作代码放在下面
//这里为了模拟方便,简单地写个死循环
while (true)
{
}
}
int main()
{
std::thread t(thread_func);
//使用花括号减小锁的粒度
{
std::unique_lock<std::mutex> lock(mymutex);
while (!success)
{
mycv.wait(lock);
}
}
std::cout << "start thread successfully." << std::endl;
t.join();
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
上述代码,发出一个创建新线程的请求后,立刻阻塞在一个条件变量上,工作线程如果成功运行起来,会发送条件变量信号告知主线程,这样主线程就知道新线程一定成功运行起来了。
基于以上思路,我们创建一组线程时,可以一个一个地创建,每成功运行一个新线程再创建下一个,确保线程组中的每一个线程都可以运行起来。示例代码如下:
#include <thread>
#include <mutex>
#include <condition_variable>
#include <iostream>
#include <vector>
#include <memory>
std::mutex mymutex;
std::condition_variable mycv;
bool success = false;
void thread_func(int no)
{
{
std::unique_lock<std::mutex> lock(mymutex);
success = true;
mycv.notify_all();
}
std::cout << "worker thread started, threadNO: " << no << std::endl;
//实际的线程执行的工作代码放在下面
//这里为了模拟方便,简单地写个死循环
while (true)
{
}
}
int main()
{
std::vector<std::shared_ptr<std::thread>> threads;
for (int i = 0; i < 5; ++i)
{
success = false;
std::shared_ptr<std::thread> spthread;
spthread.reset(new std::thread(thread_func, i));
//使用花括号减小锁的粒度
{
std::unique_lock<std::mutex> lock(mymutex);
while (!success)
{
mycv.wait(lock);
}
}
std::cout << "start thread successfully, index: " << i << std::endl;
threads.push_back(spthread);
}
for (auto& iter : threads)
{
iter->join();
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
编译上述程序并运行,运行结果如下所示:
[root@myaliyun codes]# g++ -g -o makesurethreadgroup makesurethreadgroup.cpp -std=c++0x -lpthread
[root@myaliyun codes]# ./makesurethreadgroup
worker thread started, threadNO: 0
start thread successfully, index: 0
worker thread started, threadNO: 1
start thread successfully, index: 1
worker thread started, threadNO: 2
start thread successfully, index: 2
worker thread started, threadNO: 3
start thread successfully, index: 3
worker thread started, threadNO: 4
start thread successfully, index: 4
2
3
4
5
6
7
8
9
10
11
12
可以看到,新线程挨个运行起来。当然,你不一定要使用条件变量,也可以使用其他类型的线程同步对象,如 Windows 平台的 Event 对象等等。
按照行文逻辑,这一节应该放在创建线程那一节介绍,但是由于这里使用到了线程同步对象,所以这里的内容移到了介绍完各种线程资源同步对象之后来介绍。
不知道读者注意到没有,我在介绍上述说的确保线程一定运行起来的做法时使用了两个词——“古老”和“严谨”,“严谨”不用多介绍,之所以说“古老”是因为你现在在各种新型的项目中基本上再也看不到这种确保线程运行起来的做法了,许多年以前多线程编程开始流行起来的时候,那时由于软硬件的水平限制,加之很多开发人员对多线程编程技术的不熟悉,创建新线程时确保一个线程跑起来非常必要;而如今多线程编程已经如此的司空见惯,加上操作系统和 CPU 普遍对多线程技术的支持,我们再也不用写这样的“防御“代码了;甚至只要你正确使用线程创建函数,我们实际编码时连线程函数的返回值都不必判断,基本上可以认为新线程一定会创建成功,且线程可以正常跑起来。