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() 终止程序。
相关推荐
qq_1927798713 小时前
C++模块化编程指南
开发语言·c++·算法
代码村新手13 小时前
C++-String
开发语言·c++
历程里程碑15 小时前
滑动窗口---- 无重复字符的最长子串
java·数据结构·c++·python·算法·leetcode·django
2501_9403152616 小时前
航电oj:首字母变大写
开发语言·c++·算法
lhxcc_fly17 小时前
手撕简易版的智能指针
c++·智能指针实现
浒畔居17 小时前
泛型编程与STL设计思想
开发语言·c++·算法
Fcy64817 小时前
C++ 异常详解
开发语言·c++·异常
机器视觉知识推荐、就业指导17 小时前
Qt 和 C++,是不是应该叫 Q++ 了?
开发语言·c++·qt
liu****17 小时前
三.Qt图形界面开发完全指南:从入门到掌握常用控件
开发语言·c++·qt
小龙报18 小时前
【C语言进阶数据结构与算法】单链表综合练习:1.删除链表中等于给定值 val 的所有节点 2.反转链表 3.链表中间节点
c语言·开发语言·数据结构·c++·算法·链表·visual studio