学懂C++ (二十一):高级教程——深入C++多线程开发详解

  C++多线程开发详解     

         多线程编程是现代应用程序开发中不可或缺的一部分。C++11引入了对多线程的支持,使得程序员能够更方便地利用多核处理器,提高程序的性能和响应能力。本文将全面而深入地探讨C++的多线程相关概念,包括线程的创建与管理、互斥量、条件变量、线程池以及原子操作等内容。

        C++11提供了语言层面上的多线程,包含在头文件<thread>中。它解决了跨平台的问题,提供了管理线程、保护共享数据、线程间同步操作、原子操作等类。C++11 新标准中引入了5个头文件来支持多线程编程,如下图所示:

5d4c9a23bc2447a3a2e1b3c1a75065c4.png

这个图表展示了C++11中与多线程相关的5个头文件:

  1. <thread>: 提供了创建和管理线程的功能,包括std::threadstd::this_thread
  2. <mutex>: 提供了互斥量和锁的功能,包括std::mutexstd::recursive_mutexstd::lock_guardstd::unique_lock
  3. <condition_variable>: 提供了条件变量的功能,包括std::condition_variable
  4. <atomic>: 提供了原子操作的支持,包括std::atomic
  5. <future>: 提供了异步计算的机制,包括std::futurestd::promisestd::async

以下是C++11中与多线程相关的主要头文件及其功能:

1. <thread>

  • 功能: 提供创建和管理线程的功能。
  • 常用类std::thread,用于启动线程并管理其生命周期。

2. <mutex>

  • 功能: 提供互斥量和锁的功能,确保在多线程环境中安全访问共享资源。
  • 常用类:
    • std::mutex:基本互斥量。
    • std::recursive_mutex:支持递归加锁的互斥量。
    • std::lock_guardstd::unique_lock:RAII风格的锁管理类。

3. <condition_variable>

  • 功能: 实现线程间的条件变量,用于阻塞线程直到某个条件满足。
  • 常用类std::condition_variable,用于线程之间的同步。

4. <atomic>

  • 功能: 提供原子操作的支持,确保在多线程环境中对共享数据的安全访问而无需使用互斥量。
  • 常用类std::atomic,支持各种基本数据类型。

5. <future>

  • 功能: 提供异步计算的机制,包括获取线程计算结果的功能。
  • 常用类:
    • std::future:用于获取异步操作的结果。
    • std::promise:用于在异步操作中设置值。
    • std::async:启动异步任务并返回std::future

通过使用这些头文件和类,C++开发者可以更方便地编写高效和安全的多线程程序。

1. 多线程

在C++11之前,实现多线程主要依赖于操作系统提供的API,如Linux的<pthread.h>或Windows的<windows.h>。C++11通过引入<thread>头文件,提供了语言层面的多线程支持,解决了跨平台的问题。

1.1 多进程与多线程

  • 多进程并发:将应用程序划分为多个独立的进程,每个进程有独立的地址空间。虽然多进程提供了更好的隔离性,但进程间的通信复杂且速度较慢。此外,进程创建和销毁的开销相对较大。

  • 多线程并发:在同一个进程中执行多个线程,这些线程共享相同的地址空间。线程的轻量级特性和共享内存的优势使得多线程更适合于高效的数据共享和通信。

1.2 多线程理解

在单CPU内核上,多个线程的执行是通过时间片轮转的方式实现的,并不是真正的并行计算。而在多CPU或多核系统上,可以实现真正的并行处理,从而提升程序的执行效率。

1.3 创建线程

创建线程非常简单,可以使用std::thread类来启动线程。以下是几种创建线程的方式

#include <iostream>
#include <thread>void thread_1() {std::cout << "子线程1" << std::endl;
}void thread_2(int x) {std::cout << "x:" << x << std::endl;std::cout << "子线程2" << std::endl;
}int main() {std::thread first(thread_1);         // 无参数线程std::thread second(thread_2, 100);   // 有参数线程std::cout << "主线程" << std::endl;first.join();  // 等待第一个线程结束second.join(); // 等待第二个线程结束std::cout << "子线程结束." << std::endl; // 确保所有线程完成后输出return 0;
}

 

1.4 join与detach方式

(1) join举例

使用join会使主线程等待子线程完成后再继续执行:

#include <iostream>
#include <thread>void thread_function() {while (true) {// 执行一些操作}
}int main() {std::thread th(thread_function);th.join();  // 等待线程结束return 0;
}

(2) detach举例

使用detach可以让线程在后台运行,主线程不会等待其结束:

#include <iostream>
#include <thread>void thread_function() {while (true) {std::cout << "子线程在运行" << std::endl;}
}int main() {std::thread th(thread_function);th.detach();  // 子线程在后台运行for (int i = 0; i < 10; ++i) {std::cout << "主线程在运行" << std::endl;}return 0; // 主线程结束时,子线程也可能被终止
}

1.5 this_thread

std::this_thread是一个类,它有4个功能函数,具体如下:

  • std::this_thread::get_id():获取当前线程的ID。
  • std::this_thread::yield():让当前线程放弃执行,回到就绪状态。
  • std::this_thread::sleep_for(...):暂停线程指定的时间。
  • std::this_thread::sleep_until(...):暂停线程直到指定的时间点。
#include <iostream>
#include <thread>
#include <chrono>void demonstrate_this_thread_functions() {// 获取当前线程的IDstd::cout << "Current thread ID: " << std::this_thread::get_id() << std::endl;// 让当前线程放弃执行,回到就绪状态std::cout << "Thread is yielding..." << std::endl;std::this_thread::yield();std::cout << "Thread resumed execution after yield." << std::endl;// 暂停线程指定的时间std::cout << "Thread is sleeping for 2 seconds..." << std::endl;std::this_thread::sleep_for(std::chrono::seconds(2));std::cout << "Thread woke up after sleep_for." << std::endl;// 暂停线程直到指定的时间点auto wake_up_time = std::chrono::system_clock::now() + std::chrono::seconds(2);std::cout << "Thread will sleep until a time point in the future..." << std::endl;std::this_thread::sleep_until(wake_up_time);std::cout << "Thread woke up after sleep_until." << std::endl;
}int main() {std::thread t(demonstrate_this_thread_functions);t.join();return 0;
}

 示例说明:

  1. 获取当前线程的ID

    std::cout << "Current thread ID: " << std::this_thread::get_id() << std::endl;

    通过 std::this_thread::get_id() 获取当前线程的ID并输出。

  2. 让当前线程放弃执行,回到就绪状态

    std::cout << "Thread is yielding..." << std::endl; 
    std::this_thread::yield(); 
    std::cout << "Thread resumed execution after yield." << std::endl;

    使用 std::this_thread::yield() 让当前线程放弃执行,回到就绪状态。之后线程会重新开始执行。

  3. 暂停线程指定的时间

    std::cout << "Thread is sleeping for 2 seconds..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::cout << "Thread woke up after sleep_for." << std::endl;
    

    使用 std::this_thread::sleep_for(std::chrono::seconds(2)) 暂停当前线程2秒。

  4. 暂停线程直到指定的时间点

    auto wake_up_time = std::chrono::system_clock::now() + std::chrono::seconds(2);
    std::cout << "Thread will sleep until a time point in the future..." << std::endl;
    std::this_thread::sleep_until(wake_up_time);
    std::cout << "Thread woke up after sleep_until." << std::endl;
    

    使用 std::this_thread::sleep_until(wake_up_time) 暂停当前线程直到指定的时间点(当前时间点加2秒)。

通过这个综合示例,您可以看到 std::this_thread 中各个功能函数的实际应用。

2. mutex

互斥量是用于保护共享资源的一种机制。C++11提供了几种不同类型的互斥量:

  • std::mutex:最基本的互斥量。
  • std::recursive_mutex:支持递归加锁的互斥量。
  • std::timed_mutex:支持超时的互斥量。
  • std::recursive_timed_mutex:支持递归和超时的互斥量。

2.1 lock与unlock

互斥量的基本操作包括:

  • lock():加锁。
  • unlock():解锁。
  • try_lock():尝试加锁。
    #include <iostream>
    #include <thread>
    #include <mutex>std::mutex mtx;void print_block(int n, char c) {mtx.lock(); // 上锁for (int i = 0; i < n; ++i) {std::cout << c;}std::cout << '\n';mtx.unlock(); // 解锁
    }int main() {std::thread th1(print_block, 50, '*');std::thread th2(print_block, 50, '$');th1.join();th2.join();return 0;
    }
    

     

2.2 lock_guard

std::lock_guard提供了一个简单的RAII风格的互斥量管理方式,确保在作用域内自动加锁和解锁。

#include <iostream>
#include <thread>
#include <mutex>std::mutex g_i_mutex;
int g_i = 0;void safe_increment() {std::lock_guard<std::mutex> lock(g_i_mutex);++g_i;std::cout << std::this_thread::get_id() << ": " << g_i << '\n';
}int main() {std::thread t1(safe_increment);std::thread t2(safe_increment);t1.join();t2.join();return 0;
}

 

2.3 unique_lock

std::unique_lockstd::lock_guard的增强版本,支持更复杂的锁定需求。它允许手动加锁和解锁,并可以在构造时选择不加锁。

#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx;void example() {std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 不立刻锁// do some worklock.lock(); // 后来再锁// work with the lock
}

3. condition_variable

条件变量用于线程间的同步,允许一个或多个线程等待某个条件的发生。它必须与互斥量结合使用。

3.1 wait

使用wait()时,调用线程会被阻塞,直到其他线程调用notify_one()notify_all()唤醒它。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>std::mutex mtx;
std::condition_variable cv;
int cargo = 0;void consume() {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, [] { return cargo != 0; }); // 等待条件std::cout << "消费货物: " << cargo << std::endl;cargo = 0; // 重置状态
}int main() {std::thread consumer(consume);{std::unique_lock<std::mutex> lock(mtx);cargo = 10; // 设置货物cv.notify_one(); // 通知消费者}consumer.join();return 0;
}

3.2 wait_for

使用wait_for可以设置超时,等待条件或超时返回。

#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
#include <condition_variable>std::condition_variable cv;int value;void read_value() {std::cin >> value;cv.notify_one();
}int main() {std::cout << "Please, enter an integer (I'll be printing dots): \n";std::thread th(read_value);std::mutex mtx;std::unique_lock<std::mutex> lock(mtx);while (cv.wait_for(lock, std::chrono::seconds(1))==std::cv_status::timeout) {std::cout << '.' << std::endl;}std::cout << "You entered: " << value << '\n';th.join();return 0;
}

4. 线程池

4.1 概念

线程池是一种设计模式,通过维护一定数量的线程,来处理多个任务,从而避免频繁的线程创建与销毁带来的开销。

4.2 线程池的实现

一个基本的线程池包括线程管理、工作线程和任务队列。下面是一个简单的线程池实现:

#include <iostream>
#include <thread>
#include <vector>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>class ThreadPool {
public:ThreadPool(size_t num_threads);~ThreadPool();template<class F>void enqueue(F&& f);private:std::vector<std::thread> workers;std::queue<std::function<void()>> tasks;std::mutex queue_mutex;std::condition_variable condition;bool stop;
};ThreadPool::ThreadPool(size_t num_threads) : stop(false) {for(size_t i = 0; i < num_threads; ++i) {workers.emplace_back([this] {for(;;) {std::function<void()> task;{std::unique_lock<std::mutex> lock(this->queue_mutex);this->condition.wait(lock, [this] { return this->stop || !this->tasks.empty(); });if(this->stop && this->tasks.empty()) {return;}task = std::move(this->tasks.front());this->tasks.pop();}task(); // 运行任务}});}
}ThreadPool::~ThreadPool() {{std::unique_lock<std::mutex> lock(queue_mutex);stop = true;}condition.notify_all();for(std::thread &worker: workers) {worker.join();}
}template<class F>
void ThreadPool::enqueue(F&& f) {{std::unique_lock<std::mutex> lock(queue_mutex);tasks.emplace(std::forward<F>(f));}condition.notify_one(); // 通知工作线程
}// 使用示例
int main() {ThreadPool pool(4); // 创建线程池for(int i = 0; i < 8; ++i) {pool.enqueue([i] {std::cout << "任务 " << i << " 正在执行" << std::endl;});}return 0;
}

5. 原子操作

原子操作是指在多线程环境中不会被其他线程中断的操作。C++11引入了<atomic>头文件,提供了原子类型和原子操作的支持,确保在并发环境中对某些数据的安全访问。

5.1 原子类型

C++标准库提供了多种原子类型,如std::atomic<int>std::atomic<bool>等。使用原子变量,可以避免使用互斥量来保护共享数据的访问,进而提高性能。

5.2 原子操作示例

#include <iostream>
#include <thread>
#include <atomic>std::atomic<int> counter(0);void increment() {for (int i = 0; i < 1000; ++i) {counter++; // 原子操作}
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Counter: " << counter.load() << std::endl; // 安全读取return 0;
}

 

5.3 原子操作的优点

  1. 性能更高:原子操作通常比使用互斥量更高效,因为它们不涉及线程上下文切换和锁的开销。
  2. 简单易用:在某些场景下,使用原子类型可以简化代码,减少错误的可能性。

5.4 原子操作的使用场景

  • 计数器:多个线程对一个整数计数。
  • 标志位:多个线程对某个状态的检查。
  • 状态机:在多线程环境中实现简单的状态机。

总结

C++11为多线程编程提供了强大的支持,通过引入std::threadstd::mutexstd::condition_variablestd::atomic等类,使得跨平台的多线程开发变得更加简便。通过合理的管理线程,使用互斥量保护共享资源,结合条件变量实现线程之间的同步,使用原子操作提高性能,可以有效地提高程序的性能和响应能力。此外,线程池可以帮助优化线程创建与销毁的开销,使得资源利用更加高效。希望本文能为你在C++多线程开发中提供全面的指导。

 

(C++多线程讲解后续持续更新,敬请关注!)

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/395341.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android Fragment:详解,结合真实开发场景Navigation

目录 1&#xff09;Fragment是什么 2&#xff09;Fragment的应用场景 3&#xff09;为什么使用Fragment? 4&#xff09;Fragment如何使用 5&#xff09;Fragment的生命周期 6&#xff09;Android开发&#xff0c;建议是多个activity&#xff0c;还是activity结合fragment&…

免费【2024】springboot 二手图书交易系统的设计与实现

博主介绍&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…

Llama 3.1用了1.6万个英伟达H100 GPU,耗费......

目录 Llama 3.1发布简介 Llama 3.1模型规模与训练 大模型企业发展面临的问题与困境 算力和能耗算力方面 数据和资金方面 技术和人才方面 Llama 3.1发布简介 当地时间 2024年 7月 23号&#xff0c;Meta 公司发布了迄今为止最强大的开源 AI 模型 Llama 3.1。该模型不仅规模…

Java二十三种设计模式-享元模式(12/23)

享元模式&#xff1a;高效管理大量对象的设计模式 引言 在软件开发中&#xff0c;有时需要处理大量相似或重复的对象&#xff0c;这可能导致内存使用效率低下和性能问题。享元模式提供了一种解决方案&#xff0c;通过共享对象的共同部分来减少内存占用。 基础知识&#xff0c…

谷粒商城实战笔记-145-性能压测-性能监控-jvisualvm使用-解决插件不能安装

文章目录 jvisualvm的作用安装查看gc相关信息的插件解决jvisualvm不能正常安装插件的问题1&#xff0c;查看java版本2&#xff0c;打开网址3&#xff0c;修改jvisualvm的设置 jvisualvm的作用 JVisualVM是一个集成在Java Development Kit (JDK) 中的多功能工具&#xff0c;它提…

LLMOps — 使用 BentoML 为 Llama-3 模型提供服务

使用 BentoML 和 Runpod 快速设置 LLM API 经常看到数据科学家对 LLM 的开发感兴趣&#xff0c;包括模型架构、训练技术或数据收集。然而&#xff0c;我注意到&#xff0c;很多时候&#xff0c;除了理论方面&#xff0c;许多人在以用户实际使用的方式提供这些模型时遇到了问题…

【C++】—— 类与对象(三)

【C】—— 类与对象&#xff08;三&#xff09; 4、拷贝构造函数4.1、初识拷贝构造4.1.1、为什么要传引用4.1.2、引用尽量加上 const 4.2、深入拷贝构造4.2.1、为什么要自己实现拷贝构造4.2.2、传值返回先调用拷贝构造的原因4.2.3、躺赢的 MyQueue4.2.4、传值返回与引用返回 4.…

世界500强排行榜公布 中国互联网企业表现突出

在2024年8月5日&#xff0c;《财富》杂志公布了最新的全球500强企业排行榜。 这些公司的总营收接近41万亿美元&#xff0c;占到了全球GDP的三分之一&#xff0c;其净利润同比增长2.3%&#xff0c;总计约2.97万亿美元。 中国有133家公司入选这一榜单&#xff0c;以11万亿美元的…

SpringMVC学习笔记---带你快速入门和复习

一、初识SpringMVC 1.1、什么是SpringMVC 1.1.1、什么是MVC MVC是一种软件架构模式&#xff08;是一种软件架构设计思想&#xff0c;不止Java开发中用到&#xff0c;其它语言也需要用到&#xff09;&#xff0c;它将应用分为三块&#xff1a; M&#xff1a;Model&#xff0…

数学建模--蒙特卡洛算法之电子管更换刀片寿命问题

目录 1.电子管问题重述 2.电子管问题分析 3.电子管问题求解 4.刀片问题重述 5.刀片问题分析 6.刀片问题求解 1.电子管问题重述 某设备上安装有4只型号规格完全相同的电子管&#xff0c;已知电子管寿命服从100&#xff5e;200h之间的均匀分布&#xff0e; 只要有一个电子管…

在线办公小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;通知公告管理&#xff0c;员工管理&#xff0c;部门信息管理&#xff0c;职位信息管理&#xff0c;会议记录管理&#xff0c;待办事项管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首…

Android经典实战之如何获取图片的经纬度以及如何根据经纬度获取对应的地点名称

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 在Android中&#xff0c;可以通过以下步骤获取图片的经纬度信息以及根据这些经纬度信息获取对应的地点名称。这里主要涉及两部分&#xff1a;从…

从0开始搭建vue + flask 旅游景点数据分析系统(七):可视化前后端对接实现

这一期继续编写flask后端&#xff0c;并且完成echarts折线图、柱状图和饼图的对接。 1 新增一些依赖 pip install Flask-SQLAlchemy Flask-Marshmallow pymysql修改 init.py文件&#xff0c;下面给出完整代码&#xff1a; from flask import Flask from flask_sqlalchemy im…

leetcode70_爬楼梯

思路 动态规划 爬到第n阶楼梯的方法数为&#xff1a;第n-1阶楼梯的方法数 第n-2阶楼梯的方法数 func climbStairs(n int) int {if n < 2 {return 1}dp : make([]int, n1)dp[1] 1dp[2] 2for i:3; i<n; i {dp[i] dp[i-1] dp[i-2]}return dp[n] }

Kubernetes中的PV)和 PVC深度剖析

在容器化的世界里&#xff0c;持久化存储一直是一个重要且复杂的问题。Kubernetes&#xff08;以下简称K8s&#xff09;为了解决容器中的数据持久化问题&#xff0c;提出了Persistent Volume&#xff08;PV&#xff09;和Persistent Volume Claim&#xff08;PVC&#xff09;这…

大数据信用报告查询哪家平台的比较好?

相信在搜索大数据信用的你&#xff0c;已经因为大数据信用不好受到了挫折&#xff0c;想详细了解一下自己的大数据信用&#xff0c;但是找遍了网络上的平台之后才发现&#xff0c;很多平台都只提供查询服务&#xff0c;想要找一个专业的平台查询和讲解很困难。下面本文就为大家…

LeetCode 150.逆波兰表达式求值

LeetCode 150.逆波兰表达式求值 思路&#x1f9d0;&#xff1a; 用栈存储该字符串&#xff0c;如果遇到数字就入栈&#xff0c;遇到符号就将数字出栈计算后再入栈&#xff0c;当整个字符串遍历完后&#xff0c;栈顶值就是该表达式的值。 代码&#x1f50e;&#xff1a; class …

【OpenCV C++20 学习笔记】范围阈值操作

范围阈值操作 原理HSV颜色空间RGB与HSV颜色空间之间的转换 代码实现颜色空间的转换范围阈值操作 原理 HSV颜色空间 HSV(色相hue, 饱和度sarturation, 色明度value)颜色空间与RGB颜色空间相似。hue色相通道代表颜色类型&#xff1b;saturation饱和度通道代表颜色的饱和度&…

MySQL-MHA高可用配置及故障切换

目录 案例搭建 1&#xff1a;所有服务器关闭防火墙 2&#xff1a;设置hosts文件 3&#xff1a;安装 MySQL 数据库 4&#xff1a;修改参数 5&#xff1a;安装 MHA 软件 6&#xff1a;配置无密码认证 7&#xff1a;配置 MHA 8&#xff1a;模拟 master 故障 MHA(MasterHi…

【Python修改所有可执行程序的图标】

孩子还小&#xff0c;不懂事写着玩的 警告&#xff1a;请谨慎使用。该程序会修改全系统所有可执行文件图标(其实就是注册表)&#xff0c;在重新开机后生效 演示&#xff1a; 把应用程序图标改为记事本&#x1f5d2; 原理&#xff1a; Windows 操作系统通过注册表来存储和管…