多线程开篇记录几个例子

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


C++多线程使用注意点及示例

C++11 引入了<thread>库,正式支持原生多线程编程。多线程编程的核心是线程管理共享资源同步,以下是关键注意点,以及两个示例(基础多线程用法、多线程加锁同步)。

一、C++多线程使用核心注意点

1. 环境与基础要求

  • C++11及以上标准,编译器需支持(如GCC 4.8+、MSVC 2012+、Clang 3.3+)。
  • 编译时需链接线程库(如GCC需加-pthread参数)。

2. 线程的创建与销毁

  • std::thread对象创建时立即启动线程,需传入可调用对象(函数、lambda、函数对象、类成员函数等)。
  • 必须对std::thread对象调用join()(等待线程结束)或detach()(分离线程,后台运行),否则析构时会触发std::terminate()导致程序崩溃。
  • detach()后的线程由系统接管,无法再控制,需确保线程访问的资源生命周期足够(避免悬垂引用)。

3. 数据竞争(竞态条件)

  • 多个线程同时访问共享数据 ,且至少有一个是写操作时,会出现数据竞争,导致未定义行为(结果错误、程序崩溃等)。
  • 必须通过同步机制(互斥锁、原子操作、条件变量等)保护共享数据。

4. 线程同步机制

  • 互斥锁(std::mutex) :保护临界区,同一时间只有一个线程进入。推荐使用RAII风格的std::lock_guard(自动加锁/解锁)或std::unique_lock(更灵活,支持手动加锁、超时、与条件变量配合),避免手动解锁遗漏(如异常时)。
  • 原子操作(std::atomic):适用于简单数值操作(如计数器),比互斥锁更高效,底层由CPU指令保证原子性。
  • 条件变量(std::condition_variable):用于线程间通信(如等待某个条件满足后再执行)。

5. 死锁

  • 多个线程互相持有对方需要的锁,导致无限等待。
  • 避免方式:按固定顺序加锁、使用std::lock同时加多个锁、使用C++17的std::scoped_lock(RAII风格多锁)、设置锁超时等。

6. 参数传递与资源生命周期

  • std::thread传递参数时默认是值拷贝 ,若需传递引用,需用std::ref/std::cref包装。
  • 线程函数中若访问主线程的局部变量,需确保变量生命周期长于线程(否则会出现悬垂引用)。

7. 异常处理

  • 线程函数内的未捕获异常会导致程序终止(std::terminate()),需在线程内捕获所有异常。

8. 线程数量

  • 计算密集型任务:线程数建议等于CPU核心数(避免过多上下文切换)。
  • IO密集型任务:线程数可远大于CPU核心数(利用IO等待时间)。

二、示例1:简单多线程使用(基础用法)

该示例展示:线程创建、参数传递(值/引用)、join()等待线程结束、lambda表达式作为线程函数。

cpp 复制代码
#include <iostream>
#include <thread>
#include <string>
#include <functional>  // std::ref

// 普通函数:打印数字
void printNumbers(int n, const std::string& prefix) {
    for (int i = 1; i <= n; ++i) {
        std::cout << prefix << ": " << i << std::endl;
        // 模拟耗时操作(让线程切换更明显)
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

// 类成员函数示例
class MyClass {
public:
    void printChars(char c, int times) {
        for (int i = 1; i <= times; ++i) {
            std::cout << "Char: " << c << " (" << i << ")" << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(150));
        }
    }
};

int main() {
    // 1. 线程1:调用普通函数,传递值参数
    std::thread t1(printNumbers, 5, "Thread1");

    // 2. 线程2:调用类成员函数,需传入对象指针/引用 + 函数参数
    MyClass obj;
    std::thread t2(&MyClass::printChars, &obj, 'A', 4);

    // 3. 线程3:使用lambda表达式,传递引用参数(需std::ref)
    int num = 3;
    std::string str = "Thread3";
    std::thread t3([&](int count, const std::string& s) {
        for (int i = 1; i <= count; ++i) {
            std::cout << s << ": Lambda - " << i << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(200));
        }
    }, std::ref(num), std::ref(str));  // std::ref传递引用

    // 等待所有线程结束(必须调用join,否则析构时崩溃)
    t1.join();
    t2.join();
    t3.join();

    std::cout << "所有线程执行完毕!" << std::endl;

    return 0;
}

编译运行(GCC):

bash 复制代码
g++ -std=c++11 thread_basic.cpp -o thread_basic -pthread
./thread_basic

说明

  • 三个线程会交替执行(输出顺序不固定,由操作系统调度)。
  • 线程3使用lambda表达式,通过std::ref传递引用参数,避免值拷贝。

三、示例2:多线程加锁同步(解决数据竞争)

该示例展示:多个线程修改共享变量时的数据竞争问题 ,以及使用std::mutex+std::lock_guard解决同步问题,同时对比原子操作的方案。

1. 未加锁的情况(数据竞争,结果错误)

cpp 复制代码
#include <iostream>
#include <thread>
#include <vector>

// 共享变量
int g_count = 0;

// 累加函数(未加锁)
void increment(int times) {
    for (int i = 0; i < times; ++i) {
        // 非原子操作:读取g_count → 加1 → 写回g_count
        // 多个线程同时执行时,会出现数据覆盖
        g_count++;
        // 模拟耗时操作
        std::this_thread::sleep_for(std::chrono::nanoseconds(1));
    }
}

int main() {
    const int thread_num = 5;  // 5个线程
    const int times_per_thread = 1000;  // 每个线程累加1000次
    std::vector<std::thread> threads;

    // 创建线程
    for (int i = 0; i < thread_num; ++i) {
        threads.emplace_back(increment, times_per_thread);
    }

    // 等待线程结束
    for (auto& t : threads) {
        t.join();
    }

    // 预期结果:5*1000=5000,但实际结果会小于5000(数据竞争)
    std::cout << "最终count值(未加锁):" << g_count << std::endl;

    return 0;
}

2. 加锁的情况(使用std::mutex+std::lock_guard,结果正确)

cpp 复制代码
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>  // std::mutex, std::lock_guard

// 共享变量
int g_count = 0;
// 互斥锁:保护g_count
std::mutex g_mutex;

// 累加函数(加锁)
void increment(int times) {
    for (int i = 0; i < times; ++i) {
        // RAII风格:lock_guard构造时加锁,析构时解锁(即使发生异常也会解锁)
        std::lock_guard<std::mutex> lock(g_mutex);
        // 临界区:同一时间只有一个线程执行
        g_count++;
        // 模拟耗时操作(可放在锁外,减少锁的持有时间,提高性能)
        // std::this_thread::sleep_for(std::chrono::nanoseconds(1));
    }
}

// 可选:原子操作方案(更高效,适用于简单数值操作)
#include <atomic>
std::atomic<int> g_atomic_count(0);
void increment_atomic(int times) {
    for (int i = 0; i < times; ++i) {
        g_atomic_count++;  // 原子操作,无需加锁
        std::this_thread::sleep_for(std::chrono::nanoseconds(1));
    }
}

int main() {
    const int thread_num = 5;
    const int times_per_thread = 1000;
    std::vector<std::thread> threads;

    // 方案1:使用互斥锁
    for (int i = 0; i < thread_num; ++i) {
        threads.emplace_back(increment, times_per_thread);
    }
    for (auto& t : threads) {
        t.join();
    }
    std::cout << "最终count值(加锁):" << g_count << std::endl;  // 输出5000

    // 方案2:使用原子操作(清空线程容器,重新测试)
    threads.clear();
    for (int i = 0; i < thread_num; ++i) {
        threads.emplace_back(increment_atomic, times_per_thread);
    }
    for (auto& t : threads) {
        t.join();
    }
    std::cout << "最终count值(原子操作):" << g_atomic_count << std::endl;  // 输出5000

    return 0;
}

编译运行(GCC):

bash 复制代码
g++ -std=c++11 thread_lock.cpp -o thread_lock -pthread
./thread_lock

说明

  • std::lock_guard是RAII风格的锁管理,避免了手动调用lock()unlock()的遗漏问题(如异常时)。
  • 临界区应尽可能小(如将耗时操作放在锁外),以减少线程阻塞,提高并发性能。
  • 简单的数值操作(如计数器)使用std::atomic更高效,无需互斥锁;复杂的临界区(如多个操作组成的逻辑)使用互斥锁。

总结

C++多线程编程的关键是线程管理 (正确使用join()/detach())和共享资源同步(避免数据竞争,合理使用互斥锁、原子操作、条件变量),同时需注意死锁和资源生命周期问题。上述示例覆盖了基础用法和核心同步场景,可作为多线程编程的入门参考。

相关推荐
Aevget2 小时前
MFC扩展库BCGControlBar Pro v37.2新版亮点:控件功能进一步升级
c++·mfc·界面控件
Tansmjs3 小时前
C++与GPU计算(CUDA)
开发语言·c++·算法
挖矿大亨4 小时前
c++中的函数模版
java·c++·算法
阿基米东4 小时前
基于 C++ 的机器人软件框架(具身智能)开源通信库选型分析
c++·机器人·开源
偷星星的贼115 小时前
C++中的对象池模式
开发语言·c++·算法
CN-Dust5 小时前
【C++】洛谷P3073 [USACO13FEB] Tractor S
开发语言·c++
2401_829004025 小时前
C++中的适配器模式变体
开发语言·c++·算法
平生不喜凡桃李5 小时前
二叉树遍历非递归写法: 栈
c++··二叉树遍历·非递归
-To be number.wan5 小时前
算法学习日记 | 枚举
c++·学习·算法