news 2026/1/31 4:04:51

C++多线程入门

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++多线程入门

博主介绍:程序喵大人

  • 35 - 资深C/C++/Rust/Android/iOS客户端开发
  • 10年大厂工作经验
  • 嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手
  • 《C++20高级编程》《C++23高级编程》等多本书籍著译者
  • 更多原创精品文章,首发gzh,见文末
  • 👇👇记得订阅专栏,以防走丢👇👇
    😉C++基础系列专栏
    😃C语言基础系列专栏
    🤣C++大佬养成攻略专栏
    🤓C++训练营
    👉🏻个人网站

C++11 多线程相关的知识点

本文目录:

  • 如何创建线程
  • 如何加锁
  • 如何使用原子操作
  • 如何使用条件变量
  • 如何优雅的执行异步任务

如何创建线程

C++11之前你可能使用pthread_xxx来创建线程,繁琐且不易读,C++11引入了std::thread来创建线程,支持对线程join或者detach。直接看代码:

#include<iostream>#include<thread>usingnamespacestd;intmain(){autofunc=[](){for(inti=0;i<10;++i){cout<<i<<" ";}cout<<endl;};std::threadt(func);if(t.joinable()){t.detach();}autofunc1=[](intk){for(inti=0;i<k;++i){cout<<i<<" ";}cout<<endl;};std::threadtt(func1,20);if(tt.joinable()){// 检查线程可否被jointt.join();}return0;}

上述代码中,函数funcfunc1运行在线程对象ttt中,从刚创建对象开始就会新建一个线程用于执行函数,调用join函数将会阻塞主线程,直到线程函数执行结束,线程函数的返回值将会被忽略。如果不希望线程被阻塞执行,可以调用线程对象的detach函数,表示将线程和线程对象分离,新的线程与主线程没有任何关联,线程资源在任务结束后会由操作系统自动回收。

如果没有调用join或者detach函数,假如线程函数执行时间较长,此时线程对象的生命周期结束调用析构函数清理资源,这时可能会发生crash,这里有两种解决办法,一个是调用join(),保证线程函数的生命周期和线程对象的生命周期相同,另一个是调用detach(),将线程和线程对象分离,这里需要注意,如果线程已经和对象分离,那我们就再也无法控制线程什么时候结束了,不能再通过join来等待线程执行完。

C++11还提供了获取线程id,或者系统cpu个数,获取thread native_handle,让线程休眠等功能:

std::threadt(func);cout<<"当前线程ID "<<t.get_id()<<endl;cout<<"当前cpu个数 "<<std::thread::hardware_concurrency()<<endl;autohandle=t.native_handle();// handle可用于pthread相关操作std::this_thread::sleep_for(std::chrono::seconds(1));

如何加锁

在C++11中,加锁可以使用std::mutex,mutex主要有四种:

  • std::mutex:独占的互斥量,不能递归使用,不带超时功能
  • std::recursive_mutex:递归互斥量,可重入,不带超时功能
  • std::timed_mutex:带超时的互斥量,不能递归
  • std::recursive_timed_mutex:带超时的互斥量,可以递归使用

最常用的就是std::mutex,其它三种我也没用过:

std::mutex mutex_;intmain(){autofunc1=[](intk){mutex_.lock();for(inti=0;i<k;++i){cout<<i<<" ";}cout<<endl;mutex_.unlock();};std::thread threads[5];for(inti=0;i<5;++i){threads[i]=std::thread(func1,200);}for(auto&th:threads){th.join();}return0;}

mutex还可以搭配RAII方式的锁封装类一起使用,可以动态的释放锁资源,防止线程由于编码失误导致始终持有锁。C++11主要有std::lock_guardstd::unique_lock两种RAII方式,使用方式类似:

autofunc1=[](intk){// std::lock_guard<std::mutex> lock(mutex_);std::unique_lock<std::mutex>lock(mutex_);for(inti=0;i<k;++i){cout<<i<<" ";}cout<<endl;};

std::lock_guard相比于std::unique_lock更加轻量级,少了一些成员函数,std::unique_lock类有unlock函数,可以手动释放锁,所以条件变量都配合std::unique_lock使用,而不是std::lock_guard,因为条件变量在wait时需要有手动释放锁的能力,具体关于条件变量后面会讲到。

如何使用原子操作

C++11提供了原子类型std::atomic,理论上这个T可以是任意类型,但是我平时只存放整型,别的还真的没用过,整型有这种原子变量已经足够方便,就不需要使用std::mutex来保护该变量啦。

看一个带锁计数器的代码:

structOriginCounter{// 普通的计数器intcount;std::mutex mutex_;voidadd(){std::lock_guard<std::mutex>lock(mutex_);++count;}voidsub(){std::lock_guard<std::mutex>lock(mutex_);--count;}intget(){std::lock_guard<std::mutex>lock(mutex_);returncount;}};

而用原子变量就方便的多:

structNewCounter{// 使用原子变量的计数器std::atomic<int>count;voidadd(){++count;}voidsub(){--count;}intget(){returncount.load();}};

如何使用条件变量

条件变量是C++11引入的一种同步机制,它可以阻塞一个线程或者个线程,直到有线程通知或者超时才会唤醒正在阻塞的线程,条件变量需要和锁配合使用,这里的锁就是上面介绍的std::unique_lock。这里使用条件变量实现一个CountDownLatch

classCountDownLatch{public:explicitCountDownLatch(uint32_tcount):count_(count);voidCountDown(){std::unique_lock<std::mutex>lock(mutex_);--count_;if(count_==0){cv_.notify_all();}}voidAwait(uint32_ttime_ms=0){std::unique_lock<std::mutex>lock(mutex_);while(count_>0){if(time_ms>0){cv_.wait_for(lock,std::chrono::milliseconds(time_ms));}else{cv_.wait(lock);}}}uint32_tGetCount()const{std::unique_lock<std::mutex>lock(mutex_);returncount_;}private:std::condition_variable cv_;mutablestd::mutex mutex_;uint32_tcount_=0;};

关于条件变量其实还涉及到通知丢失和虚假唤醒问题,可以看这篇文章:通知丢失和虚假唤醒。

如何优雅的执行异步任务

你可能已经猜到了,我要介绍的就是async,关于异步操作可以优先使用async,看这段代码:

#include<functional>#include<future>#include<iostream>#include<thread>usingnamespacestd;intfunc(intin){returnin+1;}intmain(){autores=std::async(func,5);// res.wait();cout<<res.get()<<endl;// 阻塞直到函数返回return0;}

使用async异步执行函数是不是方便多啦。async具体语法如下:

async(std::launch::async|std::launch::deferred,func,args...);

第一个参数是创建策略:std::launch::async表示任务执行在另一线程,std::launch::deferred表示延迟执行任务,调用get或者wait时才会执行,不会创建线程,惰性执行在当前线程。如果不明确指定创建策略,以上两个都不是async的默认策略,而是未定义,它是一个基于任务的程序设计,内部有一个

码字不易,欢迎大家点赞,关注,评论,谢谢!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/29 13:55:33

Conda环境隔离保障Qwen-Image-Edit-2509依赖安全

Conda环境隔离保障Qwen-Image-Edit-2509依赖安全 在AI模型部署日益复杂的今天&#xff0c;一个看似不起眼的Python包版本差异&#xff0c;就可能让原本运行稳定的图像编辑系统突然“失明”——比如把用户要求删除的对象变成色块&#xff0c;或将中英文文字渲染成乱码。这种问题…

作者头像 李华
网站建设 2026/1/29 14:52:50

29、AWK实用程序与脚本编程指南

AWK实用程序与脚本编程指南 在AWK编程中,有许多实用的程序和脚本技巧,能够帮助我们更高效地处理数据、生成报告以及进行文件操作。下面将介绍一些常见的应用场景及对应的实现方法。 1. 字符处理函数的改进 最初编写的程序中,字符转写功能可能需要手动实现。但后来发现Bri…

作者头像 李华
网站建设 2026/1/31 3:59:45

2025/12/15英语打卡

1.我做过最正确的事情是不再跟所有人诉说我生活中的事情 The best thing i ever did is stop telling my life to everyone. The best thing i ever did is stop telling everyone what is going on in my life. 成分&#xff1a; 主系表&#xff0c;表语 - stop xxxx重要短语&…

作者头像 李华
网站建设 2026/1/29 10:13:02

31、gawk 高级功能:双向通信、网络编程、性能分析与国际化

gawk 高级功能:双向通信、网络编程、性能分析与国际化 在数据处理和编程领域,gawk 是一款功能强大的工具。它不仅能进行基本的数据处理和脚本编写,还具备许多高级功能,如与其他进程的双向通信、网络编程、性能分析以及国际化支持等。下面我们将详细介绍这些功能。 与其他…

作者头像 李华
网站建设 2026/1/29 12:59:25

mootdx终极指南:快速掌握通达信数据读取技巧

mootdx终极指南&#xff1a;快速掌握通达信数据读取技巧 【免费下载链接】mootdx 通达信数据读取的一个简便使用封装 项目地址: https://gitcode.com/GitHub_Trending/mo/mootdx mootdx作为一款专业的Python通达信数据读取工具&#xff0c;为金融数据分析师和量化交易爱…

作者头像 李华
网站建设 2026/1/29 11:50:02

从GitHub克隆到本地运行:完整部署Stable Diffusion 3.5 FP8图文教程

从GitHub克隆到本地运行&#xff1a;完整部署Stable Diffusion 3.5 FP8图文教程 在生成式AI的浪潮中&#xff0c;谁能快速将前沿模型落地到本地设备&#xff0c;谁就掌握了内容创作的主动权。2024年发布的 Stable Diffusion 3.5&#xff08;SD3.5&#xff09; 凭借更强的语义理…

作者头像 李华