1)原子类型
原子操作默认采用的是memory_order_seq_cst内存顺序
原子类型可以是无锁的,也可以有锁
(c++17)通过静态函数std::atomic
(c++11)通过成员函数 is_lock_free()判断
2)有锁or无锁
性能:无锁操作通常比有锁操作更快,因为它们不需要操作系统级别的同步机制。无锁操作可以直接利用硬件的原子指令,这通常比软件锁更高效。
3)自旋锁与atomic_flag
atomic_flag一般初始化为std::atomic_flag ready_flag = ATOMIC_FLAG_INIT(初始值为false)
此时,atomic_flag已被设置过(clear/初始化都算),调用test_and_set, atomic_flag置为true,返回false;
此后再调用test_and_set,都会返回true
std::atomic_flag 是一个非常轻量级的原子类型,适用于需要高效同步的场景。它通常用于实现自旋锁、一次性初始化和忙等待等同步机制。
虽然它的功能相对简单,但在多线程编程中非常有用,尤其是在对性能要求较高的应用中
自旋锁(Spin Locks):
自旋锁是一种简单的锁机制,当一个线程尝试获取已经被占用的锁时,它不会立即进入睡眠状态,而是不断循环检查锁的状态,直到锁可用为止。
自旋锁适用于锁的竞争不激烈且持有时间很短的场景,因为长时间的自旋会浪费 CPU 资源。
自旋锁是一种在多线程环境下保护共享资源的同步机制。它的基本思想是,当一个线程尝试获取锁时,
如果锁已经被其他线程持有,那么该线程就会不断地循环检查锁的状态,直到成功获取到锁为止。
std::atomic_flag的test_and_set成员函数是一个原子操作,他会先检查std::atomic_flag当前的状态是否被设置过,
1 如果没被设置过(比如初始状态或者清除后),将std::atomic_flag当前的状态设置为true,并返回false。
2 如果被设置过则直接返回ture。
4) 自旋锁的逻辑
第一次lock之后,atomic_flag被改变,其他lock调用被阻塞
unlock之后,atomic_flag状态被重置,其他lock调用继续运行
自旋锁设置时使用memory_order_acquire内存次序,在清除时使用了memory_order_release内存次序。
实现:```
class SpinLock {
public:
void lock() {
//1 处
while (flag.test_and_set(std::memory_order_acquire)); // 自旋等待,直到成功获取到锁
}
void unlock() {
//2 处
flag.clear(std::memory_order_release); // 释放锁
}
private:
std::atomic_flag flag = ATOMIC_FLAG_INIT;
};
调用:类似一个普通锁
void TestSpinLock() {
SpinLock spinlock;
std::thread t1(&spinlock {
spinlock.lock();
for (int i = 0; i < 3; i++) {
std::cout << "*";
}
std::cout << std::endl;
spinlock.unlock();
});
std::thread t2([&spinlock]() {
spinlock.lock();
for (int i = 0; i < 3; i++) {
std::cout << "?";
}
std::cout << std::endl;
spinlock.unlock();
});
t1.join();
t2.join();
}
5)内存顺序
内存顺序概述
在多线程编程中,内存顺序(memory order)决定了原子操作的内存可见性和顺序。不同的内存顺序标记会影响编译器和处理器对内存访问的优化,从而影响程序的行为。C++ 提供了以下几种内存顺序标记:
std::memory_order_relaxed:最弱的内存顺序,仅保证当前操作的原子性,不提供任何额外的内存顺序保证。
std::memory_order_seq_cst:最强的内存顺序,确保所有线程看到的操作顺序是一致的,提供全局的顺序一致性。
std::memory_order_acquire:确保当前原子操作之前的所有读取操作在当前操作之后对其他线程可见。
std::memory_order_release:确保当前原子操作之前的所有写入操作在当前操作之后对其他线程可见。
std::memory_order_acq_rel:结合了 acquire 和 release 的效果,既确保当前操作之前的读取操作对其他线程可见,也确保当前操作之后的写入操作对其他线程可见。
使用:
对原子变量的 load 可以使用 memory_order_acquire 内存顺序. 这称为 acquire 操作.
对原子变量的 store 可以使用 memory_order_release 内存顺序. 这称为 release 操作.
read-modify-write 操作即读 (load) 又写 (store), 它可以使用 memory_order_acquire, memory_order_release 和 memory_order_acq_rel:
std::memory_order_consume:确保后续对依赖于当前原子操作结果的变量的读取是有序的。
使用:
多用于指针类型的原子
6)内存模型
happens-before
如果操作 a “happens-before” 操作 b, 则操作 a 的结果对于操作 b 可见
happens-before 的关系可以建立在用一个线程的两个操作之间, 也可以建立在不同的线程的两个操作之间。
标签:std,自旋,flag,atomic,memory,order,内存
From: https://www.cnblogs.com/light-LifeClub/p/18442762