C++多线程 - 无锁编程

本文探讨了互斥锁的局限性,介绍了无锁编程的概念,原子操作如何避免死锁,并通过C++示例展示了如何使用原子类型<atomic>来替代锁。重点讲解了内存访问模型和原子操作的原子库特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

互斥锁

多线程间如果要实现对共享资源的同步访问,就需要用到互斥锁。互斥锁主要是针对过程加锁,实现对共享资源的排他性访问。但使用互斥锁会影响代码的执行效率,多任务改成了单任务执行,锁的申请释放增加了访问共享资源的消耗,且可能引起线程阻塞、锁竞争、死锁、优先级反转、难以调试等问题,还容易出现死锁的情况。

无锁编程

就是不使用锁机制就可保证多线程间原子变量同步的编程。无锁(lock-free)的实现只是将多条指令合并成了一条指令形成一个逻辑完备的最小单元,通过兼容CPU指令执行逻辑形成的一种多线程编程模型。

原子操作

很多时候,对共享资源的访问主要是对某一数据结构的读写操作,如果数据结构本身就带有排他性访问的特性,也就相当于该数据结构自带一个细粒度的锁,这就是C++11提供的原子数据类型<atomic>。

原子操作:不可分割,只有未开始和已完成两种状态;
原子类型:原子库中定义的数据类型,包括通过原子类模板std::atomic<T>实例化的数据类型,也都是支持原子操作的。

原子库atomic支持的原子操作
原子库<atomic>中提供了一些基本原子类型,也可以通过原子类模板实例化一个原子对象,对原子类型的访问,最主要的就是读和写,但原子库提供的对应原子操作是load()与store(val)。原子类型支持的原子操作如下:

原子操作

原子操作中的内存访问模型
原子操作保证了对数据的访问只有未开始和已完成两种状态,不会访问到中间状态,但我们访问数据一般是需要特定顺序的,比如想读取写入后的最新数据,原子操作函数是支持控制读写顺序的,即带有一个数据同步内存模型参数std::memory_order,用于对同一时间的读写操作进行排序。C++11定义的6种类型如下:

memory_order_relaxed: 宽松操作,没有同步或顺序制约,仅对此操作要求原子性;
memory_order_release & memory_order_acquire: 两个线程A&B,A线程Release后,B线程Acquire能保证一定读到的是最新被修改过的值;这种模型更强大的地方在于它能保证发生在A-Release前的所有写操作,在B-Acquire后都能读到最新值;
memory_order_release & memory_order_consume: 上一个模型的同步是针对所有对象的,这种模型只针对依赖于该操作涉及的对象:比如这个操作发生在变量a上,而s = a + b; 那s依赖于a,但b不依赖于a; 当然这里也有循环依赖的问题,例如:t = s + 1,因为s依赖于a,那t其实也是依赖于a的;
memory_order_seq_cst: 顺序一致性模型,这是C++11原子操作的默认模型;大概行为为对每一个变量都进行Release-Acquire操作,当然这也是一个最慢的同步模型;
内存访问模型属于比较底层的控制接口,如果对编译原理和CPU指令执行过程不了解的话,容易引入bug。内存模型不是本章重点,这里不再展开介绍,后续的代码都使用默认的顺序一致性模型或比较稳妥的Release-Acquire模型。

使用原子类型替代互斥锁编程

#include "stdafx.h"
#include <chrono>
#include <atomic>
#include <thread>
#include <iostream> 

std::chrono::milliseconds interval(1000);
std::atomic<int> N(0);	//原子int类型

void func_1()
{
	for (int i = 0; i < 5; i++)
	{
		N.fetch_add(1);
		std::printf("func_1:%d\n", N.load());
		std::this_thread::sleep_for(interval);
	}
}

void func_2()
{
	for (int i = 0; i < 5; i++)
	{
		N.fetch_add(1);
		std::printf("func_2:%d\n", N.load());
		std::this_thread::sleep_for(interval);
	}
}

int main()
{
	std::thread thread_1(func_1);
	std::thread thread_2(func_2);
	thread_1.join();
	thread_2.join();

	system("pause");
	return 0;
}

该示例代码中需要注意的一点:N.fetch_add(1)和 N.load()属于两次原子操作,所以打印出的顺序并不能理想的体现出对N的同步操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值