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

news2024/11/13 19:13:43

  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() {
    // 获取当前线程的ID
    std::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 work
    lock.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.coloradmin.cn/o/1995174.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PDF怎么在线转Word?介绍四种转换方案

PDF怎么在线转Word&#xff1f;在数字化办公时代&#xff0c;文档的互换性变得尤为重要。PDF格式因其跨平台兼容性和版面固定性而广受欢迎&#xff0c;但有时我们可能需要将PDF文件转换为Word文档&#xff0c;以便进行编辑或进一步处理。以下是四种常见的在线PDF转Word的方法&a…

大数据湖体系规划与建设方案(51页PPT)

方案介绍&#xff1a; 大数据湖通过集中存储各种类型的数据&#xff08;包括结构化、半结构化和非结构化数据&#xff09;&#xff0c;提供了更加灵活、可扩展的数据处理和分析能力。其核心理念是“存储一切&#xff0c;分析一切&#xff0c;创建所需”&#xff0c;即将所有数…

【论文复现】Transformer

Transformer 前言网络架构数据处理词嵌入向量位置编码 模型定义多头注意力机制编码器Encoder解码器Decoder 前言 Transformer应用范围非常广泛&#xff0c;涵盖了自然语言处理、时间序列数据预测、图像处理等领域。由于笔者之前都是应用&#xff0c;但是对于原理并没有深刻理解…

树莓派Pico 2,RP2350 现已发售!

https://www.bilibili.com/video/BV1n5YeeMETu/?vd_sourcea637ced2b66f15709d16fcbaceeb47a9 我们很高兴地宣布推出Raspberry Pi Pico 2&#xff0c;我们的第二代微控制器板&#xff1a;采用了由我们自主设计的新款高性能安全型微控制器 RP2350。 Raspberry Pi Pico 2&#…

5 种经过验证的查询翻译技术可提高您的 RAG 性能

如何在用户输入模糊的情况下获得近乎完美的 LLM 性能 欢迎来到雲闪世界。你认为用户会向 LLM 提出完美的问题&#xff0c;这是大错特错。如果我们不直接执行&#xff0c;而是细化用户的问题&#xff0c;结果会怎样&#xff1f;这就是查询转换。 我们开发了一款应用程序&#x…

查看DrawCall流程 Unity工具 Frame Debug

切换帧率 基础面板 可以看到 我们可以通过切换面板 看DrwaCall产生 MainTex 就是材质了 如何优化&#xff1f; 合批 就会一次性直接渲染

双端队列Deque

Deque&#xff08;双端队列&#xff09;是一种允许在两端都进行插入和删除操作的线性数据结构。它在 Java Collections Framework 中作为一个重要的接口&#xff0c;具有以下结构特点&#xff1a; 1. 双端操作 两端插入和删除&#xff1a;与传统队列&#xff08;只能在一端入…

迭代次数顺序的双重性

(A,B)---6*30*2---(0,1)(1,0) 收敛误差为7e-4&#xff0c;收敛199次取迭代次数平均值&#xff0c; 让A是4a1&#xff0c;4a2&#xff0c;…&#xff0c;4a16&#xff0c;B全是0得到迭代次数的顺序就是1&#xff0c;2&#xff0c;…&#xff0c;16. 但是如果让训练集A-B矩阵的高…

kafka-go使用:以及kafka一些基本概念说明

关于kafka 作为开发人员kafka中最常关注的几个概念&#xff0c;是topic,partition和group这几个概念。topic是主题的意思&#xff0c;简单的说topic是数据主题&#xff0c;这样解释好像显得很苍白&#xff0c;只是做了个翻译。一图胜前言&#xff0c;我们还是通过图解来说明。…

PDF密码移除技巧: 五大 PDF 密码移除器

如果您想解密或删除 PDF 密码&#xff0c;该怎么办&#xff1f;PDF 是一种经常用于商业的格式&#xff0c;您可以在培训、教育和商业场合使用它。添加这些 PDF 文件的密码可以保护您的安全和隐私。因此&#xff0c;有很多 PDF 都用密码加密&#xff0c;当您想要查看这些 PDF 时…

PTrade常见问题系列22

反馈定义的上午7点执行run_daily函数&#xff0c;但是每周一上午都没法正常执行&#xff1f; 1、run_daily函数加载在initialize函数中&#xff0c;执行后才会创建定时任务&#xff1b; 2、由于周末会有例行重启操作&#xff0c;在重启以后拉起交易时相当于非交易日启动的交易…

【人工智能训练师】2 集群搭建

题目一、基础配置 core-site.xml参数配置详情 官方文档&#xff1a;http://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/core-default.xml core-default.xml与core-site.xml的功能是一样的&#xff0c;如果在core-site.xml里没有配置的属性&#xff0c…

【C++二分查找 决策包容性】1300. 转变数组后最接近目标值的数组和

本文涉及的基础知识点 C二分查找 决策包容性 LeetCode1300. 转变数组后最接近目标值的数组和 给你一个整数数组 arr 和一个目标值 target &#xff0c;请你返回一个整数 value &#xff0c;使得将数组中所有大于 value 的值变成 value 后&#xff0c;数组的和最接近 target …

【开端】JAVA中的切面使用

一、绪论 在不使用过滤器和 拦截器的前提下&#xff0c;如果统一对JAVA的 方法进行 管理。比如对一类方法或者类进行日志监控&#xff0c;前后逻辑处理。这时就可以使用到切面。它的本质还是一个拦截器。只是通过注解的方式来标识所切的方法。 二、JAVA中切面的使用实例 Aspec…

如何看待“低代码”开发平台的兴起

目录 1.概述 1.1.机遇 1.2.挑战 1.3.对开发者工作方式的影响 2.技术概览 2.1.主要特点 2.2.市场现状 2.3.主流低代码平台 2.4.分析 3.效率与质量的权衡 3.1.提高开发效率 3.2.质量与安全隐患 3.3.企业应用开发的利弊分析 4.挑战与机遇 4.1.机遇 4.2.挑战 4.3.…

为什么需要在线实时预览3D模型?如何实现?

在线实时预览3D模型在现代设计、产品开发、市场营销、以及娱乐等领域中变得越来越重要&#xff0c;原因可以归结为以下几个方面&#xff1a; 1、多平台兼容性&#xff1a; 在线实时预览通常不依赖于特定的操作系统或软件平台&#xff0c;只要设备能够访问互联网和浏览器&…

21-原理图的可读性的优化处理

1.自定义原理图尺寸 先将原理图移动到左下角 2.划分模块 3.放置模块字符串

第三期书生大模型实战营——基础岛

1.书生大模型全链路开源体系 【书生浦语大模型全链路开源开放体系】 https://www.bilibili.com/video/BV18142187g5/?share_sourcecopy_web&vd_source711f676eb7f61df7d2ea626f48ae1769 视频里介绍了书生浦语大模型的开源开放体系&#xff0c;包括了其的技术发展、模型架…

ubuntu系统下安装LNMP集成环境的详细步骤(保姆级教程)

php开发中集成环境的安装是必不可少的技能,而LNMP代表的是:Linux系统下Nginx+MySQL+PHP这种网站服务器架构。今天就给大家分享下LNMP的安装步骤。 1 Nginx安装 在安装Nginx前先执行下更新命令: sudo apt-get update 接下来开始安装Nginx, 提示:Could not get lock /v…

【mysql 第二篇章】请求到真正执行 SQL 到底是一个怎么样的过程?

从用户调用到SQL执行的流程中间发生了什么事情 1、网络请求使用 线程 来处理&#xff0c;当数据库连接池中监听到有连接请求&#xff0c;这个时候会分配一个线程来处理。 2、SQL接口 负责接收 SQL 语句&#xff0c;当线程监听到有请求和读取数据的之后&#xff0c;将 SQL 语句…