C++多线程并发编程入门(目录)
detach 的作用
detach 的作用就是让线程独自执行。
为何需要 detach
在 理解 std::thread::join 中,我们看到了,如果所有线程都是一开始就在 main 函数中创建好的,那么只需要有一个 join 函数就足够了。
但是 在 理解 std::thread::join 中,我们也已经发现了线程嵌套时,join的弊端。
为了解决 join的弊端,detach 也就出现了。
也就是 detach 仅仅是让线程独立执行,并不会让调用它的线程等待它。
这样线程运行起来之后,就和其他任何线程都没有了瓜葛。
各自独立放飞自我,没人管,没人问。
代码验证(detach)
我们将 理解 std::thread::join 中的第2个示例代码,record 线程的执行由 join 改为 detach。
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;
using namespace std::chrono;
void record()
{
// simulate expensive operation
std::this_thread::sleep_for(std::chrono::seconds(1));
cout << "record finished!" << endl;
}
void ui_fun()
{
std::cout << "starting record ...\n";
std::thread record_thread(record);
record_thread.detach();
// simulate expensive operation
std::this_thread::sleep_for(std::chrono::seconds(5));
cout << "ui_fun finished!" << endl;
}
int main()
{
auto start = std::chrono::system_clock::now();
std::cout << "starting ui_fun ...\n";
std::thread helper1(ui_fun);
std::cout << "waiting for ui_fun to finish..." << std::endl;
helper1.join();
auto elapsed = chrono::duration_cast<chrono::seconds>(system_clock::now() - start).count();
std::cout << "done! elapsed " << elapsed << " seconds.";
}
执行结果
可以看到,由于 UI线程没有等待 record 线程,所以, record 是在 UI线程执行的过程中自己独立执行完成的。
从而两个线程 ui_fun record 同时执行,整个程序执行的时间就变短了。
而此时 main 只是在等待 ui_fun线程的退出。
执行示意图

此时,只有main线程在等待UI线程,record是一个野线程。
好在record执行的时间短。
如果record执行的时间比ui_fun线程时间长呢?比如,窗口关闭了,录制写文件由于比较慢,还没完成。那岂不是要丢数据了?
detach 的弊端(野线程)
下面我们就来模拟 record 线程确实是比较慢,UI线程都被用户关闭窗口了,还没有把所有的录制数据写入到本地文件。
代码验证(detach野线程执行耗时操作)
完整代码:我们把 record 线程的执行时间由 原来的 1秒 改为 11秒。
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;
using namespace std::chrono;
void record()
{
// simulate expensive operation
std::this_thread::sleep_for(std::chrono::seconds(11));
cout << "record finished!" << endl;
}
void ui_fun()
{
std::cout << "starting record ...\n";
std::thread record_thread(record);
record_thread.detach();
// simulate expensive operation
std::this_thread::sleep_for(std::chrono::seconds(5));
cout << "ui_fun finished!" << endl;
}
int main()
{
auto start = std::chrono::system_clock::now();
std::cout << "starting ui_fun ...\n";
std::thread helper1(ui_fun);
std::cout << "waiting for ui_fun to finish..." << std::endl;
helper1.join();
auto elapsed = chrono::duration_cast<chrono::seconds>(system_clock::now() - start).count();
std::cout << "done! elapsed " << elapsed << " seconds.";
}
执行结果

我们可以看到,main 线程等到UI线程执行完就退出了。
main退出意味着整个进程退出,操作系统会回收一切。
这个时候 record 线程虽然还没执行完,但是也被操作系统(强拆)回收了。
这太糟糕了。程序没有正常的退出。
执行示意图

所以,main还是要等待所有线程退出的。
作为所有模块最后退出的保障,main必须是最后一个退出才行。
detach 的正确用法
通过上面的案例分析,我们可以知道 detach 线程函数内部必须要要是一个无限循环。
并且有退出条件,而退出条件必须由外部设置。
同时,detach 线程内部要一直等待这个退出条件。
main 函数需要增加代码来触发这个 detach 线程的退出条件。从而变成了 detach 在启动的时候是以 detach 方式启动(不阻塞其他任何线程),同时 main 以代码的方式 等待(join)这个detach 线程。
由于具体的代码比较复杂,这里就不做完整的展示了。
我会在 本专栏 C++多线程并发编程入门(目录) 的后续文章中展示。