C++核心之多线程

目录

[C++ 高并发编程实战:线程使用、同步机制与无锁队列详解](#C++ 高并发编程实战:线程使用、同步机制与无锁队列详解)

一、线程创建的四种常用方式

[1. 普通函数作为线程入口](#1. 普通函数作为线程入口)

[2. 带参数函数作为线程入口](#2. 带参数函数作为线程入口)

[3. 类成员函数作为线程入口](#3. 类成员函数作为线程入口)

[4. Lambda 表达式创建线程](#4. Lambda 表达式创建线程)

二、线程同步与互斥核心机制

[1. 互斥锁 std::mutex](#1. 互斥锁 std::mutex)

[2. RAII 锁模板 std::lock_guard](#2. RAII 锁模板 std::lock_guard)

[3. 条件变量 std::condition_variable](#3. 条件变量 std::condition_variable)

[三、原子操作 std::atomic 与无锁编程基础](#三、原子操作 std::atomic 与无锁编程基础)

四、高性能无锁队列实现

[1. 队列节点结构](#1. 队列节点结构)

[2. 无锁队列完整实现](#2. 无锁队列完整实现)

五、总结


C++ 高并发编程实战:线程使用、同步机制与无锁队列详解

在高性能后端、游戏服务器、基础组件开发等场景中,C++ 多线程与无锁编程是提升程序并发能力、充分利用多核 CPU 的关键技术。本文将从线程基础创建方式入手,讲解常用线程同步方案,并深入到原子操作与无锁队列实现,由浅入后构建一套完整的高并发编程知识体系。

一、线程创建的四种常用方式

C++11 标准库提供了 std::thread,让线程创建变得简洁且统一,日常开发中主要有以下四种使用形式。

1. 普通函数作为线程入口

这是最基础的线程使用方式,直接将普通函数作为线程执行体。

cpp 复制代码
void thread_func1() {
    // 线程业务逻辑
}

int main() {
    std::thread t1(thread_func1);
    t1.join();
    return 0;
}

2. 带参数函数作为线程入口

线程支持传入多个参数,编译器会自动完成参数转发,适配绝大多数带参业务逻辑。

cpp 复制代码
void thread_func2(int a, std::string b) {
    // 使用传入的参数
}

int main() {
    std::thread t2(thread_func2, 10, "hello");
    t2.join();
    return 0;
}

3. 类成员函数作为线程入口

面向对象开发中,常需要将类成员函数运行在线程中,使用时必须传入对象地址。

cpp 复制代码
class Test {
public:
    void func() {}
};

int main() {
    Test obj;
    std::thread t3(&Test::func, &obj);
    t3.join();
    return 0;
}

4. Lambda 表达式创建线程

Lambda 写法轻便直观,适合简短、一次性的线程逻辑,是现代 C++ 高频用法。

cpp 复制代码
int main() {
    std::thread t4( {
        // 直接编写线程执行代码
    });
    t4.join();
    return 0;
}

二、线程同步与互斥核心机制

多线程并发访问共享资源时,必须通过同步机制避免数据竞争,常用方案如下。

1. 互斥锁 std::mutex

最基础的互斥手段,通过手动加锁、解锁保护临界区资源。

cpp 复制代码
std::mutex mtx;
void func() {
    mtx.lock();
    // 临界区操作
    mtx.unlock();
}

2. RAII 锁模板 std::lock_guard

利用栈对象生命周期自动管理锁,构造加锁、析构解锁,避免手动管理导致的死锁风险。

cpp 复制代码
void func() {
    std::lock_guard<std::mutex> lock(mtx);
    // 临界区代码
}

具体说明:

  1. 类封装特性:lock_guard 是 C++11 <mutex>头文件)中的模板类,构造函数接收一个互斥锁(如std::mutex),自动调用锁的 lock()方法获取锁;析构函数自动调用锁的unlock ()` 方法释放锁,全程无需手动操作。
  2. 与智能指针的共性:两者都通过 "类的生命周期" 管理资源 ------ 智能指针封装堆内存(构造时分配 / 接收内存,析构时自动释放),lock_guard 封装互斥锁(构造时加锁,析构时解锁),均避免了手动管理资源(释放内存、解锁)导致的泄漏或死锁。
  3. 细微区别:lock_guard 不可拷贝、不可移动,生命周期严格绑定到当前作用域(离开作用域自动析构解锁);而智能指针(如 unique_ptr)支持移动,shared_ptr 支持共享所有权,功能更灵活,但核心的 RAII 封装思想完全一致。

lock_guard` 和 `unique_lock` 均是 C++11 标准库(`<mutex>` 头文件)中,用于**封装互斥锁、实现 RAII 机制**的模板类,二者核心目的一致,且存在明确的继承/设计关联,具体联系如下:

与unique_lock的联系:

核心联系(本质共性)

  1. 核心设计思想一致 :两者均基于 RAII(资源获取即初始化) 机制,通过类的生命周期管理互斥锁------构造函数自动调用 `mutex::lock()` 获取锁,析构函数自动调用 `mutex::unlock()` 释放锁,彻底避免手动加锁/解锁导致的死锁(忘记解锁)或资源泄漏。

  2. 核心功能一致:核心作用都是"安全管理互斥锁",保护多线程中的临界区,避免数据竞争,简化多线程同步代码的编写。

  3. 底层依赖一致:两者都必须依赖 `std::mutex`(或其他可锁类型,如 `std::recursive_mutex`)才能工作,自身不具备"锁"的功能,仅负责对锁的生命周期进行封装管理。

  4. 不可拷贝特性一致 :两者均被设计为 不可拷贝(拷贝构造函数和拷贝赋值运算符被删除),避免多个锁管理对象同时管理同一个互斥锁,导致重复解锁或解锁异常。

关键补充

`unique_lock` 是 `lock_guard` 的 增强版,在继承 RAII 核心思想的基础上,扩展了更灵活的功能,可以理解为: `unique_lock = lock_guard + 灵活解锁/延迟加锁/条件变量配合`

正因为二者核心设计一致,`unique_lock` 才能兼容 `lock_guard` 的所有基础用法(如简单的临界区保护),同时解决 `lock_guard` 的局限性。

注意事项

  • 两者均不可拷贝,但 `unique_lock` 支持移动语义(可通过 `std::move` 转移所有权),`lock_guard` 不支持移动,生命周期严格绑定到当前作用域。

  • `lock_guard` 仅支持"构造时加锁、析构时解锁",无法手动解锁、无法延迟加锁;`unique_lock` 支持 `unlock()` 手动解锁、构造时延迟加锁(传入 `std::defer_lock`),适配条件变量(`condition_variable::wait()`)等复杂场景。

  • 性能上,`lock_guard` 更轻量(无额外状态维护),`unique_lock` 因支持灵活操作,有轻微性能开销,简单场景优先用 `lock_guard`,复杂场景用 `unique_lock`。

3. 条件变量 std::condition_variable

用于线程间等待与唤醒,常配合 std::unique_lock 实现生产者 - 消费者模型,支持等待时自动释放锁。

cpp 复制代码
std::condition_variable cv;
void consumer() {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock,  {
        // 满足条件才继续执行
        return true;
    });
}
cpp 复制代码
int main()
{
	int x = 1;
	condition_variable cv;
	mutex mtx;
	bool flag = false;

	//线程1打印奇数
	thread t1([&]() {
		for (int i = 1;i <= 100;i++)
		{
			unique_lock<mutex>lock(mtx);
			//确保线程1先启动
			if (flag != false)
				cv.wait(lock);
			cout <<"线程1:" << x++ << endl;
			flag = true;
			//唤醒线程2
			cv.notify_one();
		}

		
	});

	//线程2打印偶数
	thread t2([&]() {	
		for (int i = 1;i <= 100;i++)
		{
			unique_lock<mutex>lock(mtx);
			if (flag != true)
				cv.wait(lock);
			cout << "线程2:" << x++ << endl;
			flag = false;
			//唤醒线程1
			cv.notify_one();
		}	
	});
	//线程等待释放
	t1.join();
	t2.join();
	return 0;
}

三、原子操作 std::atomic 与无锁编程基础

在高并发场景下,锁的开销可能成为性能瓶颈,std::atomic 提供了原子操作,可在不加锁的情况下保证变量访问的线程安全性,是无锁编程的基石。

原子类型支持 load() 读取、store() 赋值、++/-- 等原子运算,而核心的 CAS(比较并交换)操作 compare_exchange_strong,更是实现无锁数据结构的关键。其原理为:先比较内存值与预期值是否相等,相等则更新为目标值,否则不做修改,通过循环重试保证操作最终成功。

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include<thread>
#include<iostream>
#include <mutex>
#include<atomic>
using namespace std;

int main()
{
	atomic<size_t> x = 0;
	int n = 100000;

	//线程1打印奇数
	thread t1([&]() {
		for (int i = 1;i <= n;i++)
		{
			x++;
		}
		});

	//线程2打印偶数
	thread t2([&]() {
		for (int i = 1;i <= n;i++)
		{
			x++;	
		}
		});
	//线程等待释放
	t1.join();
	t2.join();
	cout << x << endl;
	return 0;
}

四、高性能无锁队列实现

无锁队列基于原子指针与 CAS 实现,避免锁竞争带来的阻塞,在高并发读写场景下性能优势显著。

1. 队列节点结构

节点内部使用原子指针指向下一节点,保证多线程下指针修改安全。

cpp 复制代码
template <typename T>
struct Node {
    T data;
    std::atomic<Node*> next;

    Node(T val) : data(val), next(nullptr) {}
};

2. 无锁队列完整实现

通过原子头尾指针,结合 CAS 循环实现无锁入队、出队操作,并使用虚拟头节点简化边界处理。

cpp 复制代码
template <typename T>
class LockFreeQueue {
private:
    std::atomic<Node<T>*> head;
    std::atomic<Node<T>*> tail;

public:
    LockFreeQueue() {
        Node<T>* dummy = new Node<T>(T());
        head = dummy;
        tail = dummy;
    }

    // 无锁入队
    void enqueue(const T& data) {
        Node<T>* new_node = new Node<T>(data);
        Node<T>* old_tail = nullptr;

        while (true) {
            old_tail = tail.load();
            Node<T>* next = old_tail->next.load();

            if (tail == old_tail) {
                if (next == nullptr) {
                    if (old_tail->next.compare_exchange_weak(next, new_node)) {
                        tail.compare_exchange_strong(old_tail, new_node);
                        return;
                    }
                }
            }
        }
    }

    // 无锁出队
    bool dequeue(T& data) {
        Node<T>* old_head = nullptr;

        while (true) {
            old_head = head.load();
            Node<T>* tail_ptr = tail.load();
            Node<T>* next = old_head->next.load();

            if (old_head == tail_ptr) {
                if (next == nullptr) return false;
                tail.compare_exchange_strong(tail_ptr, next);
                continue;
            }

            data = next->data;
            if (head.compare_exchange_strong(old_head, next)) {
                delete old_head;
                return true;
            }
        }
    }
};

五、总结

本文从基础线程创建,到锁机制同步,再到原子操作与无锁队列,覆盖了 C++ 高并发编程的核心知识点。std::thread 提供了灵活的线程使用方式,互斥锁与条件变量满足常规同步需求,而基于 std::atomic 的无锁队列,则为高性能场景提供了更优的解决方案。

在实际开发中,简单场景优先使用锁保证安全,高并发瓶颈场景可采用无锁结构优化,合理搭配不同方案,才能写出高效、稳定的并发程序。

相关推荐
꧁꫞꯭零꯭点꯭꫞꧂3 小时前
前端面试题3
开发语言·前端·javascript
南境十里·墨染春水3 小时前
C++ 笔记 function 函数包装器模板
开发语言·c++·笔记
研來如此3 小时前
tinyxml2 常用读取接口对照表
xml·c++·tinyxml2
MC皮蛋侠客3 小时前
C++中使用Redis指南:基于redis-plus-plus库
开发语言·c++·redis
星晨雪海3 小时前
Redis-逻辑查询详情讲解
java·开发语言
大鹏说大话3 小时前
Java线程池调优实战:从核心参数到避坑指南
java·开发语言
lolo大魔王3 小时前
Go语言的基础语法
开发语言·后端·golang
小陈工3 小时前
Python Web开发入门(八):用户认证系统实现,给你的应用加上安全锁
开发语言·前端·数据库·python·安全·django·sqlite
铅笔侠_小龙虾3 小时前
Miniconda + Poetry 实战
开发语言·python