首页 > 编程语言 >C++ 多线程详解:从基础到应用

C++ 多线程详解:从基础到应用

时间:2024-09-10 18:21:42浏览次数:3  
标签:std include int C++ 任务 详解 线程 多线程

目录

在现代应用中,多线程成为了提升程序性能的重要工具。特别是当我们希望充分利用多核 CPU 的计算能力时,C++ 提供了强大的多线程支持,可以并发地执行多个任务。今天,我们将通过易懂的讲解与实际的代码示例,帮助你掌握 C++ 中的多线程编程。

一、什么是多线程?

简单来说,多线程允许程序同时执行多个任务。每个任务运行在不同的线程中,这些线程共享同一个进程的资源。使用多线程可以有效提高程序的执行效率,特别是在处理计算密集型或 I/O 密集型任务时。

打个比方:假设你在厨房做饭,你一个人做饭(单线程)可能需要准备、洗菜、炒菜,然后等米饭煮好才能吃。如果你能请到助手(多线程),一个人洗菜,一个人炒菜,这样两件事情可以同时进行,效率自然会大大提高。

多线程的好处
提高效率:让多个任务并行执行,减少等待时间。
增强用户体验:如在游戏或应用中,可以将耗时操作(比如加载资源)放在后台执行,避免界面卡顿。
多核 CPU 的利用:现代计算机通常是多核的,使用多线程可以充分利用多个 CPU 核心并发执行任务。

二、C++ 中的多线程支持

C++ 在 C++11 标准中引入了 std::thread 类,极大简化了多线程编程。我们可以通过 std::thread 来创建、管理线程,并通过锁机制解决线程之间的数据竞争问题。

  1. 基本的多线程示例
    让我们从一个最基本的例子开始,创建一个新线程并在其中执行一段代码。
#include <iostream>
#include <thread>

// 定义一个简单的函数
void printMessage() {
    std::cout << "Hello from thread!" << std::endl;
}

int main() {
    // 创建并启动线程,执行 printMessage 函数
    std::thread t(printMessage);

    // 主线程等待子线程执行完毕
    t.join();

    return 0;
}

代码解释:
std::thread t(printMessage);:创建一个新线程并执行 printMessage 函数。
t.join();:主线程等待子线程执行完毕后再继续。注意:如果不调用 join(),主线程可能会提前结束,从而导致子线程无法正确执行。

  1. 传递参数给线程
    线程不仅可以执行不带参数的函数,还可以传递参数。在 C++ 中,你可以通过简单的值传递或引用传递的方式,向线程函数传递参数。
#include <iostream>
#include <thread>

void printNumber(int n) {
    std::cout << "Number from thread: " << n << std::endl;
}

int main() {
    int number = 5;
    std::thread t(printNumber, number);

    t.join();
    return 0;
}

代码解释:
std::thread t(printNumber, number);:将 number 传递给线程执行的函数 printNumber。

  1. 使用 Lambda 表达式创建线程
    有时候,为了简单和灵活,可以使用 Lambda 表达式 来直接定义一个线程执行的任务。Lambda 表达式可以捕获外部变量,方便多线程编程。
#include <iostream>
#include <thread>

int main() {
    int value = 10;

    // 使用 Lambda 表达式创建线程
    std::thread t([value]() {
        std::cout << "Lambda thread value: " << value << std::endl;
    });

    t.join();
    return 0;
}

代码解释:
std::thread t(value { … });:通过 Lambda 表达式直接在创建线程时定义线程要执行的任务。

  1. 多线程中的共享数据与同步
    多个线程可以共享同一块数据,但这可能导致数据竞争(多个线程同时修改同一个数据导致错误)。为了解决这个问题,我们需要使用**互斥量(mutex)**来保护共享资源。

数据竞争示例(错误示范):

#include <iostream>
#include <thread>

int counter = 0;  // 共享数据

void increment(int n) {
    for (int i = 0; i < n; ++i) {
        ++counter;  // 每个线程同时修改 counter,可能引发数据竞争
    }
}

int main() {
    std::thread t1(increment, 1000);
    std::thread t2(increment, 1000);

    t1.join();
    t2.join();

    std::cout << "Final counter value: " << counter << std::endl;  // 结果可能不正确
    return 0;
}

使用互斥量解决数据竞争:

#include <iostream>
#include <thread>
#include <mutex>

int counter = 0;  // 共享数据
std::mutex mtx;   // 互斥量

void increment(int n) {
    for (int i = 0; i < n; ++i) {
        std::lock_guard<std::mutex> lock(mtx);  // 加锁,保护共享数据
        ++counter;
    }
}

int main() {
    std::thread t1(increment, 1000);
    std::thread t2(increment, 1000);

    t1.join();
    t2.join();

    std::cout << "Final counter value: " << counter << std::endl;
    return 0;
}

代码解释:
std::mutex mtx;:声明一个互斥量,用来保护共享数据。
std::lock_guardstd::mutex lock(mtx);:确保在操作共享数据时,其他线程无法同时访问该数据。

  1. 线程池的概念
    线程池是一种优化多线程程序性能的方式。通过提前创建一组线程并不断复用它们,可以避免频繁地创建和销毁线程,进而提高性能。

尽管 C++ 标准库中没有直接提供线程池的实现,但我们可以自己实现一个简单的线程池。

#include <iostream>
#include <vector>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>

// 线程池类,用于管理一组线程并执行任务
class ThreadPool {
public:
    // 构造函数,创建指定数量的线程
    ThreadPool(size_t numThreads);
    // 析构函数,确保线程池在销毁时正确停止所有线程
    ~ThreadPool();

    // 将任务添加到任务队列中
    void enqueue(std::function<void()> task);

private:
    // 工作线程列表
    std::vector<std::thread> workers;
    // 任务队列,存储要执行的任务
    std::queue<std::function<void()>> tasks;
    // 互斥量,用于保护任务队列的访问
    std::mutex queueMutex;
    // 条件变量,用于线程等待任务
    std::condition_variable condition;
    // 标志位,指示线程池是否停止
    bool stop;

    // 工作线程执行的函数
    void workerThread();
};

// 构造函数,创建 numThreads 个线程并启动
ThreadPool::ThreadPool(size_t numThreads) : stop(false) {
    // 启动每个线程,执行 workerThread 函数
    for (size_t i = 0; i < numThreads; ++i) {
        workers.emplace_back([this] { workerThread(); });
    }
}

// 析构函数,等待所有线程执行完毕后销毁
ThreadPool::~ThreadPool() {
    {
        // 获取互斥锁,设置停止标志位
        std::unique_lock<std::mutex> lock(queueMutex);
        stop = true;
    }
    // 通知所有等待的线程
    condition.notify_all();
    // 等待每个线程完成并加入到主线程中
    for (std::thread &worker : workers) {
        worker.join();
    }
}

// 将新任务添加到任务队列中
void ThreadPool::enqueue(std::function<void()> task) {
    {
        // 获取互斥锁,保证任务队列操作的线程安全
        std::unique_lock<std::mutex> lock(queueMutex);
        // 将任务添加到队列中
        tasks.push(task);
    }
    // 通知一个等待的线程去执行新添加的任务
    condition.notify_one();
}

// 工作线程执行的函数
void ThreadPool::workerThread() {
    while (true) {
        std::function<void()> task;
        {
            // 获取互斥锁,安全访问任务队列
            std::unique_lock<std::mutex> lock(queueMutex);
            // 等待,直到有任务或线程池被停止
            condition.wait(lock, [this] { return stop || !tasks.empty(); });
            // 如果线程池停止且任务队列为空,则退出线程
            if (stop && tasks.empty()) {
                return;
            }
            // 从任务队列中取出任务
            task = std::move(tasks.front());
            tasks.pop();
        }
        // 执行任务
        task();
    }
}

// 主函数,创建线程池并提交任务
int main() {
    ThreadPool pool(4);  // 创建一个有 4 个线程的线程池

    // 添加 8 个任务到线程池
    for (int i = 0; i < 8; ++i) {
        pool.enqueue([i] {
            std::cout << "Task " << i << " is being processed" << std::endl;
        });
    }

    // 主线程休眠 2 秒,等待任务执行完成
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return 0;
}

代码解释:
线程池在初始化时创建了一组线程。
enqueue() 方法将任务添加到任务队列中,线程池中的线程会从队列中取出任务并执行。
线程池的好处在于减少了频繁创建和销毁线程的开销,提升了并发执行效率。

三、总结

通过这篇文章,你应该对 C++ 中的多线程编程有了一个全面的理解。从基础的线程创建到使用互斥量保护共享资源,再到实现线程池,C++ 提供了强大而灵活的工具来编写并发程序。

多线程编程的核心是:并发执行、共享资源、同步保护。我们要合理利用线程,同时避免常见的错误,比如数据竞争和死锁。在实际项目中,多线程编程不仅能提升程序的性能,还能带来更好的用户体验。

希望通过本文的学习,你能够在自己的项目中更加自如地使用 C++ 多线程技术!

标签:std,include,int,C++,任务,详解,线程,多线程
From: https://blog.csdn.net/weixin_52734695/article/details/142105937

相关文章

  • 【系统架构设计师-2024年真题】案例分析-答案及详解
    更多内容请见:备考系统架构设计师-核心总结索引文章目录【材料1】(软件架构设计与评估)问题1问题2问题3【材料2】(系统设计与建模)问题1问题2问题3问题4【材料3】(嵌入式)问题1问题2问题3【材料4】(数据库缓存)问题1问题2问题3【材料5】(W......
  • 进程间通信之消息队列详解
    目录一、什么是消息队列?二、消息队列的优缺点优点:缺点:三、消息队列的实现原理四、消息队列的使用场景五、消息队列的编程实现(C语言示例)1.创建消息队列2.发送消息3.接收消息4.删除消息队列六、总结        在现代操作系统中,进程间通信(IPC)是一个至关......
  • C++环境搭建(Visual Studio 2022软件安装)
    安装环境:Windows11家庭中文版VisualStudio2022下载地址:        https://pan.baidu.com/s/15U8AEIwThxp-fAZFJqnCgQ    提取码:0000 安装步骤:        1.下载后选择安装包进行解压。    2.以管理员身份运行安装程序。(企业版功能最全,这......
  • Linux iostat 命令详解
    Linuxiostat命令详解在Linux系统管理中,监控磁盘I/O性能是一项至关重要的任务。iostat是sysstat包中的一个实用工具,用于监控和显示系统输入输出设备和CPU的使用情况。它提供了丰富的数据,帮助系统管理员识别并解决潜在的I/O瓶颈问题。本文将详细介绍iostat命令的使用方法和关键参......
  • 线程池以及详解使用@Async注解异步处理方法
    目录一.什么是线程池:二.使用线程池的好处:三.线程池的使用场景:四.使用线程池来提高Springboot项目的并发处理能力:1.在application.yml配置文件中配置:2.定义配置类来接受配置文件内的属性值:3.启用异步支持:4.实例: 五.详细解析@Async注解的使用:1.@Async注解作用:2.@Asyn......
  • Kafka集群搭建与基本原理详解
    目录一、Kafka介绍1、MQ的作用MQ的作用主要有以下三个方面:1.异步2.解耦3.削峰2、为什么要用Kafka(特点)二、Kafka快速上手1、实验环境2、单机服务体验1、启动Kafka之前需要先启动Zookeeper。2、启动Kafka。3、简单收发消息4、其他消费模式指定消费进度分组......
  • 平台开发到落地详解:从食堂采购系统源码到可视化供应链管理数据大屏
    随着数字化转型的加速,越来越多的企业和组织开始重视供应链的智能化与可视化管理。在食堂采购领域,供应链管理的复杂性与日俱增,而传统的手工操作往往效率低下、容易出错。因此,开发食堂采购系统并结合可视化数据大屏的解决方案,成为了许多企业提高运营效率、优化采购流程的关键手段。 ......
  • C++入门知识
    目录C++是什么C++关键字(c++98)命名空间(namespace)命名空间的定义 命名空间使用声明和定义 C++的输入输出缺省函数缺省函数是什么?全缺省半缺省注意一:半缺省只能从右向左给,并且不能中断 缺省函数不能同时在声明和定义中出现缺省的参数只能是全局的或者常数函数......
  • C++中STL容器的使用
    容器一些基本操作语法vector初始化操作vector<int>a;//声明向量vector<int>a(10);//声明一个初始大小为10的向量vector<int>a(10,1);//初始大小为10,且值都为1的向量vector<int>b(a);//声明并用向量a初始化向量bvector<int>b(a.begin(),a.begin()+3);//将......
  • 最简单C++线程和互斥锁使用示例
    std::thread是C++11标准库中引入的一个类,用于表示一个独立的执行线程。而std::mutex是C++11中提供的一种互斥锁,用于在多个线程间同步对共享数据的访问,以避免数据竞争和条件竞争。下面将分别介绍std::thread和std::mutex的基本使用,并通过一个示例展示它们的结合使用......