原子操作子系统(CategoryAtomic)
原子操作相关功能。
重要提示:如果你并非无锁并发编程领域的专家,请勿使用本模块中的任何函数。你应当使用完整的互斥锁来保护数据结构,而非直接使用原子操作。
郑重提醒:此处暗藏风险!
你可以通过以下资源了解无锁编程及其可能引发的微妙问题:
https://learn.microsoft.com/en-us/windows/win32/dxtecharts/lockless-programming
此外,这些资源也包含大量优质内容:
这些原子操作不一定都通过处理器专属的原子指令实现:
- 条件允许时,会直接使用处理器原生原子指令;
- 条件不允许时,会基于底层原子操作实现的锁来模拟原子行为。
所有修改内存的原子操作均为全内存屏障(full memory barriers),可确保内存操作的顺序性和可见性。
函数
- SDL_AddAtomicInt:原子化地为 SDL_AtomicInt 类型变量执行加法操作(返回操作前的原值)
- SDL_AddAtomicU32:原子化地为 SDL_AtomicU32 类型变量执行加法操作(返回操作前的原值)
- SDL_CompareAndSwapAtomicInt:对原子整型执行比较并交换(CAS)操作(值匹配时替换,返回操作前的值)
- SDL_CompareAndSwapAtomicPointer:对原子指针执行比较并交换(CAS)操作(指针匹配时替换,返回操作前的指针)
- SDL_CompareAndSwapAtomicU32:对原子32位无符号整型执行比较并交换(CAS)操作(值匹配时替换,返回操作前的值)
- SDL_GetAtomicInt:原子化地读取 SDL_AtomicInt 类型变量的值(确保读取到最新值)
- SDL_GetAtomicPointer:原子化地读取原子指针的值(确保读取到最新指针)
- SDL_GetAtomicU32:原子化地读取 SDL_AtomicU32 类型变量的值(确保读取到最新值)
- SDL_LockSpinlock:自旋锁加锁(忙等方式,不放弃CPU,适合短时间锁定场景)
- SDL_MemoryBarrierAcquireFunction:执行获取(Acquire)内存屏障(确保后续内存操作可见前面的写入)
- SDL_MemoryBarrierReleaseFunction:执行释放(Release)内存屏障(确保前面的内存操作对其他线程可见)
- SDL_SetAtomicInt:原子化地设置 SDL_AtomicInt 类型变量的值(确保写入立即对其他线程可见)
- SDL_SetAtomicPointer:原子化地设置原子指针的值(确保指针写入立即对其他线程可见)
- SDL_SetAtomicU32:原子化地设置 SDL_AtomicU32 类型变量的值(确保写入立即对其他线程可见)
- SDL_TryLockSpinlock:尝试获取自旋锁(非阻塞,获取失败直接返回,不忙等)
- SDL_UnlockSpinlock:自旋锁解锁(释放自旋锁,允许其他线程获取)
数据类型
- SDL_SpinLock:自旋锁类型(基于原子操作实现的忙等锁,无内核态切换开销)
结构体
- SDL_AtomicInt:原子整型结构体(封装int类型,支持原子操作)
- SDL_AtomicU32:原子32位无符号整型结构体(封装uint32_t类型,支持原子操作)
枚举
- (无)
宏
- SDL_AtomicDecRef:原子化地减少引用计数(基于SDL_AtomicInt实现,返回操作后的值)
- SDL_AtomicIncRef:原子化地增加引用计数(基于SDL_AtomicInt实现,返回操作后的值)
- SDL_CompilerBarrier:编译器屏障(阻止编译器重排指令,不影响CPU执行顺序)
- SDL_CPUPauseInstruction:CPU暂停指令(自旋锁中使用,减少忙等时的CPU占用)
- SDL_MemoryBarrierAcquire:获取(Acquire)内存屏障宏(封装SDL_MemoryBarrierAcquireFunction)
- SDL_MemoryBarrierRelease:释放(Release)内存屏障宏(封装SDL_MemoryBarrierReleaseFunction)
FreeBASIC 示例代码
' 引入 SDL 相关声明(需确保 FreeBASIC 已链接 SDL3 库)
#Include "SDL.bi"
' 补充原子操作结构体的定义(FreeBASIC 绑定可能缺失)
Type SDL_AtomicInt
value As Integer
End Type
Type SDL_AtomicU32
value As UInteger
End Type
' 补充原子操作函数声明(FreeBASIC 绑定可能缺失)
Declare Function SDL_AddAtomicInt CDecl (ByVal a As SDL_AtomicInt Ptr, ByVal v As Integer) As Integer
Declare Function SDL_AddAtomicU32 CDecl (ByVal a As SDL_AtomicU32 Ptr, ByVal v As UInteger) As UInteger
Declare Function SDL_CompareAndSwapAtomicInt CDecl (ByVal a As SDL_AtomicInt Ptr, ByVal oldval As Integer, ByVal newval As Integer) As Integer
Declare Function SDL_CompareAndSwapAtomicU32 CDecl (ByVal a As SDL_AtomicU32 Ptr, ByVal oldval As UInteger, ByVal newval As UInteger) As UInteger
Declare Function SDL_GetAtomicInt CDecl (ByVal a As SDL_AtomicInt Ptr) As Integer
Declare Function SDL_GetAtomicU32 CDecl (ByVal a As SDL_AtomicU32 Ptr) As UInteger
Declare Sub SDL_SetAtomicInt CDecl (ByVal a As SDL_AtomicInt Ptr, ByVal v As Integer)
Declare Sub SDL_SetAtomicU32 CDecl (ByVal a As SDL_AtomicU32 Ptr, ByVal v As UInteger)
Declare Sub SDL_LockSpinlock CDecl (ByVal lock As SDL_SpinLock Ptr)
Declare Function SDL_TryLockSpinlock CDecl (ByVal lock As SDL_SpinLock Ptr) As Integer
Declare Sub SDL_UnlockSpinlock CDecl (ByVal lock As SDL_SpinLock Ptr)
' 原子变量定义
Dim Shared As SDL_AtomicInt g_atomicCounter ' 原子整型计数器
Dim Shared As SDL_AtomicU32 g_atomicU32Value ' 原子32位无符号整型
Dim Shared As SDL_SpinLock g_spinLock = 0 ' 自旋锁(初始值0为未锁定)
Dim Shared As Any Ptr g_atomicPointer = NULL ' 原子指针(示例)
Dim Shared As Boolean g_quitThreads = False ' 线程退出标志
' 线程1:原子加法操作
Function AtomicAddThread CDecl (ByVal data As Any Ptr) As Integer
Dim As ZString Ptr threadName = Cast(ZString Ptr, data)
Dim As Integer threadId = SDL_GetCurrentThreadID()
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s (ID: %d) 启动,执行原子加法操作", threadName, threadId)
While (Not g_quitThreads)
' 原子化地给计数器加1(返回操作前的值)
Dim As Integer oldValue = SDL_AddAtomicInt(@g_atomicCounter, 1)
Dim As Integer currValue = SDL_GetAtomicInt(@g_atomicCounter)
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s - 原子加法:旧值=%d,新值=%d", threadName, oldValue, currValue)
' 原子化地给无符号整型加5
Dim As UInteger oldU32 = SDL_AddAtomicU32(@g_atomicU32Value, 5)
Dim As UInteger currU32 = SDL_GetAtomicU32(@g_atomicU32Value)
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s - 原子U32加法:旧值=%u,新值=%u", threadName, oldU32, currU32)
' 模拟工作
SDL_Delay(300)
Wend
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s (ID: %d) 退出", threadName, threadId)
Return 0
End Function
' 线程2:CAS(比较并交换)操作
Function AtomicCASThread CDecl (ByVal data As Any Ptr) As Integer
Dim As ZString Ptr threadName = Cast(ZString Ptr, data)
Dim As Integer threadId = SDL_GetCurrentThreadID()
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s (ID: %d) 启动,执行CAS操作", threadName, threadId)
While (Not g_quitThreads)
' CAS操作:只有当计数器值等于10时,才将其设置为0
Dim As Integer expected = 10
Dim As Integer desired = 0
Dim As Integer oldValue = SDL_CompareAndSwapAtomicInt(@g_atomicCounter, expected, desired)
If (oldValue = expected) Then
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s - CAS成功:计数器从%d重置为%d", threadName, expected, desired)
Else
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s - CAS失败:期望值=%d,实际值=%d", threadName, expected, oldValue)
End If
' 模拟工作
SDL_Delay(800)
Wend
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s (ID: %d) 退出", threadName, threadId)
Return 0
End Function
' 线程3:自旋锁操作
Function SpinlockThread CDecl (ByVal data As Any Ptr) As Integer
Dim As ZString Ptr threadName = Cast(ZString Ptr, data)
Dim As Integer threadId = SDL_GetCurrentThreadID()
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s (ID: %d) 启动,使用自旋锁", threadName, threadId)
While (Not g_quitThreads)
' 获取自旋锁(忙等,适合短时间锁定)
SDL_LockSpinlock(@g_spinLock)
' 临界区(自旋锁保护,独占访问)
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s - 获取自旋锁,执行临界区操作", threadName)
SDL_Delay(100) ' 短时间操作(自旋锁适合短临界区)
' 释放自旋锁
SDL_UnlockSpinlock(@g_spinLock)
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s - 释放自旋锁", threadName)
' 模拟工作
SDL_Delay(500)
Wend
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s (ID: %d) 退出", threadName, threadId)
Return 0
End Function
' 线程4:引用计数原子操作
Function RefCountThread CDecl (ByVal data As Any Ptr) As Integer
Dim As ZString Ptr threadName = Cast(ZString Ptr, data)
Dim As Integer threadId = SDL_GetCurrentThreadID()
Static As SDL_AtomicInt refCount ' 静态引用计数(原子整型)
' 初始化引用计数
If (SDL_GetAtomicInt(@refCount) = 0) Then
SDL_SetAtomicInt(@refCount, 1)
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s - 初始化引用计数为1", threadName)
End If
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s (ID: %d) 启动,操作引用计数", threadName, threadId)
' 增加引用计数(使用宏)
Dim As Integer newRef = SDL_AtomicIncRef(@refCount)
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s - 增加引用计数:%d", threadName, newRef)
' 模拟使用资源
SDL_Delay(1000)
' 减少引用计数(使用宏)
newRef = SDL_AtomicDecRef(@refCount)
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s - 减少引用计数:%d", threadName, newRef)
' 引用计数为0时释放资源
If (newRef = 0) Then
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s - 引用计数为0,释放资源", threadName)
End If
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s (ID: %d) 退出", threadName, threadId)
Return 0
End Function
' 主程序
Sub Main()
' 初始化 SDL
If (SDL_Init(SDL_INIT_THREADS) < 0) Then
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL 初始化失败:%s", SDL_GetError())
Exit Sub
End If
' 初始化原子变量
SDL_SetAtomicInt(@g_atomicCounter, 0)
SDL_SetAtomicU32(@g_atomicU32Value, 0)
' 创建线程
Dim As String t1Name = "原子加法线程"
Dim As String t2Name = "CAS操作线程"
Dim As String t3Name = "自旋锁线程"
Dim As String t4Name = "引用计数线程1"
Dim As String t5Name = "引用计数线程2"
Dim As SDL_Thread Ptr t1 = SDL_CreateThread(@AtomicAddThread, t1Name, StrPtr(t1Name))
Dim As SDL_Thread Ptr t2 = SDL_CreateThread(@AtomicCASThread, t2Name, StrPtr(t2Name))
Dim As SDL_Thread Ptr t3 = SDL_CreateThread(@SpinlockThread, t3Name, StrPtr(t3Name))
Dim As SDL_Thread Ptr t4 = SDL_CreateThread(@RefCountThread, t4Name, StrPtr(t4Name))
Dim As SDL_Thread Ptr t5 = SDL_CreateThread(@RefCountThread, t5Name, StrPtr(t5Name))
If (t1 = NULL Or t2 = NULL Or t3 = NULL Or t4 = NULL Or t5 = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_THREAD, "创建线程失败:%s", SDL_GetError())
g_quitThreads = True
SDL_Quit()
Exit Sub
End If
' 主线程运行 6 秒
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "主线程运行 6 秒...")
SDL_Delay(6000)
' 通知线程退出
g_quitThreads = True
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "主线程:通知所有线程退出")
' 等待所有线程结束
Dim As Integer result
SDL_WaitThread(t1, @result)
SDL_WaitThread(t2, @result)
SDL_WaitThread(t3, @result)
SDL_WaitThread(t4, @result)
SDL_WaitThread(t5, @result)
' 打印最终原子变量值
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "最终原子计数器值:%d", SDL_GetAtomicInt(@g_atomicCounter))
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "最终原子U32值:%u", SDL_GetAtomicU32(@g_atomicU32Value))
' 退出 SDL
SDL_Quit()
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "程序正常退出")
End Sub
' 补充引用计数宏的实现(FreeBASIC 绑定可能缺失)
#Define SDL_AtomicIncRef(a) (SDL_AddAtomicInt(a, 1) + 1)
#Define SDL_AtomicDecRef(a) (SDL_AddAtomicInt(a, -1) - 1)
' 运行主程序
Main()
核心知识点补充
-
原子操作核心特性:
- 不可分割性:原子操作的执行过程无法被其他线程中断,要么完全执行,要么完全不执行;
- 内存可见性:修改原子变量的操作会立即对所有线程可见(全内存屏障);
- 无锁特性:无需互斥锁即可实现线程安全,避免内核态切换开销,但编程复杂度极高。
-
CAS 操作(Compare and Swap):
- 核心逻辑:
如果变量值等于期望值,则替换为目标值,返回原值; - 典型用法:实现无锁数据结构(如无锁队列、栈),需配合循环重试;
- 注意事项:可能引发 ABA 问题(变量值从 A→B→A,CAS 误判为未修改),需额外处理。
- 核心逻辑:
-
自旋锁适用场景:
- 临界区执行时间极短(微秒级),且线程数不超过 CPU 核心数;
- 避免在单核 CPU 或临界区较长的场景使用(会导致其他线程无法执行);
SDL_CPUPauseInstruction可减少自旋锁忙等时的 CPU 占用率。
-
内存屏障分类:
- Acquire(获取)屏障:确保屏障后的读操作能看到其他线程的写操作;
- Release(释放)屏障:确保屏障前的写操作对其他线程可见;
- 编译器屏障:仅阻止编译器指令重排,不影响 CPU 执行顺序。
-
原子操作 vs 互斥锁: 特性 原子操作 互斥锁 开销 极低(用户态,无切换) 较高(可能触发内核态) 适用场景 简单变量操作(计数、标志) 复杂数据结构/长临界区 编程复杂度 极高(易出隐蔽 bug) 较低(逻辑清晰) 阻塞方式 无阻塞(CAS 需循环重试) 阻塞(线程进入等待队列)
总结
-
核心优势:
- 提供跨平台的原子操作接口,屏蔽不同处理器的原子指令差异;
- 支持整型、指针、32位无符号整型的原子操作,覆盖基础无锁编程需求;
- 自旋锁基于原子操作实现,适合短临界区的高效同步。
-
使用建议:
- 非无锁编程专家,优先使用互斥锁而非原子操作;
- 原子操作仅用于简单变量(计数器、标志位),复杂逻辑仍需互斥锁;
- 自旋锁仅用于临界区极短的场景,避免滥用;
- CAS 操作需处理 ABA 问题,必要时使用版本号辅助判断。
-
关键接口:
- 基础原子操作:
SDL_AddAtomicInt()/SDL_SetAtomicInt()/SDL_GetAtomicInt(); - CAS 操作:
SDL_CompareAndSwapAtomicInt()/SDL_CompareAndSwapAtomicPointer(); - 自旋锁:
SDL_LockSpinlock()/SDL_TryLockSpinlock()/SDL_UnlockSpinlock(); - 内存屏障:
SDL_MemoryBarrierAcquire()/SDL_MemoryBarrierRelease(); - 引用计数:
SDL_AtomicIncRef()/SDL_AtomicDecRef()。
- 基础原子操作:
评论一下?