CppGuide社区 CppGuide社区
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
  • C++语言面试问题集锦
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • 🔥C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 🔥从零用C语言写一个Redis
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 🔥使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
  • C++语言面试问题集锦
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • 🔥C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 🔥从零用C语言写一个Redis
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 🔥使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
  • 第1章高频C++11重难点知识解析

  • 第2章Linux GDB高级调试指南

  • 第3章C++多线程编程从入门到进阶

    • 3.1 线程的基本概念
    • 3.2 线程基本操作
    • 3.3 线程函数传C++类实例指针惯用法
    • 3.4 整型变量的原子操作
    • 3.5 Linux线程同步对象
    • 3.6 Windows 线程资源同步对象
    • 3.7 C++ 11/14/17 线程同步对象
    • 3.8 如何确保创建的线程一定运行起来?
    • 3.9 多线程使用锁实践经验总结
    • 3.10 线程局部存储
    • 3.11 C 库的非线程安全函数
    • 3.12 线程池与队列系统的设计
    • 3.13 纤程(Fiber)与协程(Coroutine)
    • 3.14 本章总结
  • 第4章C++网络编程重难点解析

  • 第5章网络通信故障排查常用命令

  • 第6章高性能网络通信协议设计精要

  • 第7章高性能服务结构设计

  • 第8章Redis 网络通信模块源码分析

  • 第9章后端服务重要模块设计探索

  • C++后端开发进阶
  • 第3章C++多线程编程从入门到进阶
zhangxf
2023-04-05

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;
}
1
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;
}
1
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
1
2
3
4
5
6
7
8
9
10
11
12

可以看到,新线程挨个运行起来。当然,你不一定要使用条件变量,也可以使用其他类型的线程同步对象,如 Windows 平台的 Event 对象等等。

按照行文逻辑,这一节应该放在创建线程那一节介绍,但是由于这里使用到了线程同步对象,所以这里的内容移到了介绍完各种线程资源同步对象之后来介绍。

不知道读者注意到没有,我在介绍上述说的确保线程一定运行起来的做法时使用了两个词——“古老”和“严谨”,“严谨”不用多介绍,之所以说“古老”是因为你现在在各种新型的项目中基本上再也看不到这种确保线程运行起来的做法了,许多年以前多线程编程开始流行起来的时候,那时由于软硬件的水平限制,加之很多开发人员对多线程编程技术的不熟悉,创建新线程时确保一个线程跑起来非常必要;而如今多线程编程已经如此的司空见惯,加上操作系统和 CPU 普遍对多线程技术的支持,我们再也不用写这样的“防御“代码了;甚至只要你正确使用线程创建函数,我们实际编码时连线程函数的返回值都不必判断,基本上可以认为新线程一定会创建成功,且线程可以正常跑起来。

上次更新: 2025/04/01, 20:53:14
3.7 C++ 11/14/17 线程同步对象
3.9 多线程使用锁实践经验总结

← 3.7 C++ 11/14/17 线程同步对象 3.9 多线程使用锁实践经验总结→

最近更新
01
第二章 关键字static及其不同用法
03-27
02
第一章 auto与类型推导
03-27
03
C++语言面试问题集锦 目录与说明
03-27
更多文章>
Copyright © 2024-2025 沪ICP备2023015129号 张小方 版权所有
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式