c++ 11 之 并发与多线程

C++11 首次将并发与多线程 纳入标准库(<thread><mutex><condition_variable><future> 等头文件),结束了此前依赖 POSIX Threads(pthread)、Windows API 等平台特定接口的局面,实现了跨平台的多线程编程。

一. 线程的基础概念

在深入 API 前,先明确多线程的基础概念:

概念 说明
线程(Thread) 进程内的执行流,共享进程资源(内存、文件句柄),但有独立的栈、寄存器。
数据竞争(Data Race) 多个线程同时访问同一共享资源,且至少有一个线程是写操作,导致未定义行为。
同步(Synchronization) 协调线程执行顺序 / 访问资源的方式(如互斥锁、条件变量),避免数据竞争。
原子操作(Atomic) 不可中断的操作(如 ++),无需锁即可保证多线程安全。
阻塞(Blocking) 线程暂停执行,等待某个条件满足(如锁释放、数据就绪)。

二. 核心组件详细解

线程管理

std::thread 是 C++11 封装线程的核心类,用于创建、管理线程的生命周期。

  • 创建线程:构造函数接收可调用对象(函数、Lambda、函数对象)及参数。

  • 等待线程join() 阻塞当前线程,直到目标线程执行完毕。

  • 分离线程detach() 将线程设为 "后台线程",与主线程分离,无需 join()(线程结束后资源自动回收)。

  • 线程标识get_id() 返回线程 ID(std::thread::id 类型)。

c++ 复制代码
#include <iostream>
#include <thread>
#include <chrono>

// 普通函数作为线程入口
void print_num(int num, const std::string& msg) {
    // 模拟耗时操作
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Thread " << num << ": " << msg << std::endl;
}

int main() {
    // 1. 创建线程:传入函数+参数
    std::thread t1(print_num, 1, "Hello C++11");
    std::thread t2([]() { // Lambda 作为线程入口
        std::cout << "Thread 2: Lambda is running\n";
    });

    // 2. 获取线程ID
    std::cout << "t1 ID: " << t1.get_id() << std::endl;
    std::cout << "t2 ID: " << t2.get_id() << std::endl;

    // 3. 等待线程结束(必须:否则主线程退出会导致程序崩溃)
    t1.join();
    t2.join();

    // 4. 分离线程示例(后台运行)
    std::thread t3(print_num, 3, "Detached thread");
    t3.detach(); // 分离后,t3不再joinable()
    std::cout << "Main thread exit pre \n";
    
    // 给分离线程一点执行时间(否则主线程退出太快,分离线程可能没执行)
    std::this_thread::sleep_for(std::chrono::seconds(2));

    std::cout << "Main thread exit after \n";
    return 0;
}

互斥锁

(1) 基础互斥锁 std::mutex

核心方法:

  • lock():加锁(若锁已被占用,阻塞至锁释放);
  • unlock():解锁(必须与 lock() 配对,否则死锁);
  • try_lock():尝试加锁(成功返回 true,失败返回 false,不阻塞)。
c++ 复制代码
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

int g_count = 0;
mutex g_mutex;

void increment() {
    for (int i = 0; i < 100000; ++i) {
        g_mutex.lock(); // 加锁
        ++g_count;      // 临界区(共享资源操作)
        g_mutex.unlock(); // 解锁
    }
}

int main() {
    thread t1(increment);
    thread t2(increment);
    t1.join();
    t2.join();
    cout << "Final count: " << g_count << endl; // 预期 200000
    return 0;
}
(2)智能锁:std::lock_guard(推荐)

std::lock_guard 是 RAII 封装的互斥锁,构造时加锁,析构时自动解锁,避免手动 unlock() 遗漏导致死锁。

c++ 复制代码
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

int g_count = 0;
mutex g_mutex;

void increment() {
    for (int i = 0; i < 100000; ++i) {
        lock_guard<mutex> lock(g_mutex); // 构造加锁,出作用域自动解锁
        ++g_count;
    }
}

int main() {
    thread t1(increment);
    thread t2(increment);
    t1.join();
    t2.join();
    cout << "Final count: " << g_count << endl; // 预期 200000
    return 0;
}
(3)灵活锁:std::unique_lock
  • 延迟加锁(std::defer_lock);
  • 手动加锁 / 解锁(lock()/unlock());
  • 转移锁所有权(std::move);
  • 配合条件变量使用。
c++ 复制代码
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

int g_count = 0;
mutex g_mutex;

void increment() {
    for (int i = 0; i < 100000; ++i) {
        unique_lock<mutex> lock(g_mutex, defer_lock); // 延迟加锁
        lock.lock(); // 手动加锁
        ++g_count;
        lock.unlock(); // 手动解锁(可提前释放锁,提升并发)
    }
}

void increment2() {
    for (int i = 0; i < 100000; ++i) {
        unique_lock<mutex> lock(g_mutex); 
        ++g_count;
    }
}

int main() {
    thread t1(increment);
    thread t2(increment2);
    t1.join();
    t2.join();
    cout << "Final count: " << g_count << endl; // 预期 200000
    return 0;
}
(4)其他互斥锁类型
类型 特点
std::recursive_mutex 递归互斥锁,允许同一线程多次加锁(需对应次数解锁),适合递归函数
std::timed_mutex 带超时的互斥锁,try_lock_for()/try_lock_until() 支持超时等待
std::recursive_timed_mutex 递归 + 超时的互斥锁

条件变量 std::condition_variable

<condition_variable> 头文件提供条件变量,用于线程间同步通信(如 "生产者 - 消费者" 模型),让线程等待某个条件满足后再执行

核心机制:

  • 线程 A:等待条件变量,释放锁并阻塞;
  • 线程 B:满足条件后,通知条件变量,唤醒线程 A;
  • 线程 A 被唤醒后,重新获取锁并检查条件。

示例:生产者 - 消费者模型

c++ 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
using namespace std;

queue<int> g_queue;
mutex g_mutex;
condition_variable g_cv;
const int MAX_QUEUE_SIZE = 5;

// 生产者
void producer(int id) {
    for (int i = 0; i < 10; ++i) {
        unique_lock<mutex> lock(g_mutex);
        // 等待:队列未满
        g_cv.wait(lock, []() { return g_queue.size() < MAX_QUEUE_SIZE; });
        
        // 生产数据
        int data = id * 10 + i;
        g_queue.push(data);
        cout << "Producer " << id << " push: " << data << endl;
        
        // 通知消费者
        g_cv.notify_one();
    }
}

// 消费者
void consumer(int id) {
    for (int i = 0; i < 10; ++i) {
        unique_lock<mutex> lock(g_mutex);
        // 等待:队列非空
        g_cv.wait(lock, []() { return !g_queue.empty(); });
        
        // 消费数据
        int data = g_queue.front();
        g_queue.pop();
        cout << "Consumer " << id << " pop: " << data << endl;
        
        // 通知生产者
        g_cv.notify_one();
    }
}

int main() {
    thread p1(producer, 1);
    thread p2(producer, 2);
    thread c1(consumer, 1);
    thread c2(consumer, 2);
    
    p1.join();
    p2.join();
    c1.join();
    c2.join();
    return 0;
}

关键方法:

  • wait(lock, pred):释放锁并阻塞,被唤醒后重新获取锁,检查 pred(条件满足则继续,否则再次阻塞);
  • notify_one():唤醒一个等待的线程;
  • notify_all():唤醒所有等待的线程;
  • wait_for(lock, duration, pred):超时等待,超时后返回 pred 的结果。

原子操作:std::atomic

<atomic> 头文件提供原子类型,用于无锁的共享资源操作,比互斥锁更高效(底层由 CPU 原子指令实现)。

std::atomic<T> 封装基础类型(intboolpointer 等),支持原子的读写、自增、自减等操作。

c++ 复制代码
#include <iostream>
#include <thread>
#include <atomic>
using namespace std;

atomic<int> g_count(0); // 原子变量

void increment() {
    for (int i = 0; i < 100000; ++i) {
        ++g_count; // 原子自增,无需加锁
    }
}

int main() {
    thread t1(increment);
    thread t2(increment);
    t1.join();
    t2.join();
    cout << "Final count: " << g_count << endl; // 预期 200000
    return 0;
}
load() 原子读取值
store(val) 原子写入值
exchange(val) 原子交换值(返回旧值)
compare_exchange_weak(expected, desired) CAS 操作:若当前值等于 expected,则替换为 desired(弱版本可能伪失败)
compare_exchange_strong(expected, desired) CAS 操作(强版本,无伪失败)

适用场景

  • 简单的计数器、标志位(如 atomic<bool> is_running);
  • 无锁队列、无锁栈等高性能并发数据结构;
  • 注意:std::atomic 不支持复杂类型(如 std::string),仅支持基础类型和指针。

关键注意事项

  • joinable() 检查 :线程对象在 join()/detach() 前必须是 joinable()(即未被移动、未被 join/detach),否则调用 join() 会崩溃。

  • 线程移动std::thread 不可拷贝,但可移动(符合 C++11 移动语义):

c++ 复制代码
#include <iostream>
#include <thread>
#include <chrono>

// 普通函数作为线程入口
void print_num(int num, const std::string& msg) {
    // 模拟耗时操作
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Thread " << num << ": " << msg << std::endl;
}

int main() {
    // 1. 创建线程:传入函数+参数
    std::thread t4(print_num, 4, "Moved thread");
    std::thread t5 = std::move(t4); // t4变为非joinable,t5接管线程
    t5.join();
}
  • 主线程退出 :若主线程先退出,未 join()/detach() 的线程会触发 std::terminate() 终止程序。
相关推荐
꧁Q༒ོγ꧂16 小时前
C++ 入门完全指南(六)--指针与动态内存
开发语言·c++
say_fall16 小时前
C++ 类与对象易错点:初始化列表顺序 / 静态成员访问 / 隐式类型转换
android·java·开发语言·c++
ChoSeitaku17 小时前
16.C++入门:list|手撕list|反向迭代器|与vector对比
c++·windows·list
Qhumaing17 小时前
C++学习:【PTA】数据结构 7-1 实验6-1(图-邻接矩阵)
c++·学习·算法
No0d1es17 小时前
2025年12月 GESP CCF编程能力等级认证C++一级真题
开发语言·c++·青少年编程·gesp·ccf
2301_7737303117 小时前
系统编程—在线商城信息查询系统
c++·html
郝学胜-神的一滴17 小时前
深入理解Linux中的Try锁机制
linux·服务器·开发语言·c++·程序人生
散峰而望18 小时前
【算法竞赛】顺序表和vector
c语言·开发语言·数据结构·c++·人工智能·算法·github
cpp_250118 小时前
B3927 [GESP202312 四级] 小杨的字典
数据结构·c++·算法·题解·洛谷