快速概述:基于自旋锁的原子交换答案
这段代码片段是我们将要构建的。
对于在多核系统上使用基本自旋锁(高速、无上下文切换互斥)方法,这是自旋锁最好的应用场景,您可以像这样对变量值进行原子交换(在本例中为指针):
int *a = &some_int1;
int *b = &some_int2;
spinlock_t spinlock = SPINLOCK_UNLOCKED;
atomic_swap_int_ptr(&spinlock, &a, &b);
以下是详细信息:
C11中通用自旋锁方法(用于包装需要原子操作的任何“关键部分”)
如果您的架构支持使用数组“对”进行无锁原子交换(在此处的主要答案中提供),则请考虑。它可能是无锁的,因此比使用互斥锁或自旋锁更有效率。
我想深入探讨一下这个问题,并尝试使用自旋锁。我没有对各种技术(数组“对”,互斥锁和自旋锁)进行速度分析,但对我来说,进行这样的分析非常有趣和学术意义,以了解它们在速度上的比较。
无论如何,正如我所说的:您也可以使用互斥锁。
或者,如果您不希望线程在互斥锁不可用时进入睡眠状态,则可以通过使用自旋锁来提高效率,因为自旋锁必须自旋的次数通常很少,因此自旋比使线程进入睡眠状态并进行整个上下文切换更快。
C11有一整套原子类型和函数,可以让您轻松实现自己的自旋锁以完成此操作。
但请记住,在裸机(没有操作系统)单核(处理器)系统上,基本自旋锁不能使用,因为如果主上下文获取锁,然后在锁仍被占用时,ISR触发并尝试获取锁,那么ISR将无限期地阻塞。
为了避免这种情况,必须使用更复杂的锁定机制,例如带有超时的自旋锁、自动重新调度自身以稍后时间获取锁的ISR、自动放弃等。
因此,自旋锁最好保留给具有操作系统和调度程序的多核系统。
使用_Atomic
类型在C11中实现基本自旋锁
#include <stdbool.h>
#include <stdatomic.h>
#define SPINLOCK_UNLOCKED 0
#define SPINLOCK_LOCKED 1
typedef volatile atomic_bool spinlock_t;
bool atomic_test_and_set(spinlock_t* spinlock)
{
bool spinlock_val_old = atomic_exchange(spinlock, SPINLOCK_LOCKED);
return spinlock_val_old;
}
void spinlock_lock(spinlock_t* spinlock)
{
while (atomic_test_and_set(spinlock) == SPINLOCK_LOCKED) {};
}
void spinlock_unlock(spinlock_t* spinlock)
{
*spinlock = SPINLOCK_UNLOCKED;
}
现在,您可以像这样在
多核系统中使用自旋锁:
void atomic_swap_int_ptr(spinlock_t * spinlock, int ** ptr1, int ** ptr2)
{
spinlock_lock(spinlock)
int * temp = *ptr1;
*ptr1 = *ptr2;
*ptr2 = temp;
spinlock_unlock(spinlock)
}
int *a = &some_int1;
int *b = &some_int2;
spinlock_t spinlock = SPINLOCK_UNLOCKED;
atomic_swap_int_ptr(&spinlock, &a, &b);
参考资料
- https://en.wikipedia.org/wiki/Test-and-set#Pseudo-C_implementation_of_a_spin_lock
- ***** C11并发支持库和
atomic_bool
和其他类型:https://en.cppreference.com/w/c/thread
- C11
atomic_exchange()
函数:https://en.cppreference.com/w/c/atomic/atomic_exchange
其他链接
- 请注意,您也可以使用C11的
struct atomic_flag
以及其相应的atomic_flag_test_and_set()
函数,而不是像我上面所做的那样使用atomic_exchange()
创建自己的atomic_test_and_set()
函数。
- C11中的
_Atomic
类型:https://en.cppreference.com/w/c/language/atomic
- C++11使用头文件
<atomic>
实现自旋锁
待办事项(最新的在顶部)
- [ ] 20230418: 升级此实现并使用以下更改进行速度分析。请参阅C并发文档:https://en.cppreference.com/w/c/thread:
- [ ] 使用
atomic_flag
而不是atomic_bool
:https://en.cppreference.com/w/c/atomic/atomic_flag(重点添加):
atomic_flag
是一种原子布尔类型。与其他原子类型不同,它保证是无锁的。与atomic_bool
不同,atomic_flag
不提供加载或存储操作。
- 确保使用
ATOMIC_FLAG_INIT
进行初始化。
- 此外,使用
atomic_flag_test_and_set()
代替我上面的自定义atomic_test_and_set()
,并使用atomic_flag_clear()
来清除或“取消设置”标志。
- [ ] 20220922: 此实现需要进行修改以添加安全的反死锁机制,例如自动延迟、超时和重试,以防止在单核心和裸机系统上出现死锁。请参见我的笔记:Add basic mutex (lock) and spin lock implementations in C11, C++11, AVR, and STM32和What are the various ways to disable and re-enable interrupts in STM32 microcontrollers in order to implement atomic access guards?
atomic_int
在几乎所有架构上都必须是非无锁的(因为它不能通过其他方式合成而不锁定)。 - Peter Cordes