互斥锁子系统(CategoryMutex)
SDL 提供了多种线程同步原语,用于保障多线程程序的线程安全。本文档无法全面覆盖线程安全这一复杂主题,但理解这些同步原语的用途、价值和正确用法,是编写正确且安全的多线程程序的关键。
SDL 提供的核心同步原语包括:
- 互斥锁(Mutexes):
SDL_CreateMutex()—— 独占式资源访问控制 - 读写锁(Read/Write locks):
SDL_CreateRWLock()—— 读共享、写独占的资源访问控制 - 信号量(Semaphores):
SDL_CreateSemaphore()—— 控制并发访问数量的同步机制 - 条件变量(Condition variables):
SDL_CreateCondition()—— 线程间的条件等待/通知机制
此外,SDL 还提供了 SDL_InitState 数据类型,用于确保当多个线程同时尝试首次使用某个资源时,只有一个线程负责该资源的初始化/反初始化操作。
函数
- SDL_BroadcastCondition:广播唤醒所有等待该条件变量的线程
- SDL_CreateCondition:创建条件变量(用于线程间条件等待/通知)
- SDL_CreateMutex:创建互斥锁(基础的独占式同步锁)
- SDL_CreateRWLock:创建读写锁(读共享、写独占的高级锁)
- SDL_CreateSemaphore:创建信号量(指定初始计数值)
- SDL_DestroyCondition:销毁条件变量(释放关联资源)
- SDL_DestroyMutex:销毁互斥锁(释放关联资源)
- SDL_DestroyRWLock:销毁读写锁(释放关联资源)
- SDL_DestroySemaphore:销毁信号量(释放关联资源)
- SDL_GetSemaphoreValue:获取信号量的当前计数值
- SDL_LockMutex:加锁互斥锁(阻塞式,获取不到锁则等待)
- SDL_LockRWLockForReading:为读操作加读写锁(共享锁,允许多线程同时读)
- SDL_LockRWLockForWriting:为写操作加读写锁(独占锁,仅允许一个线程写)
- SDL_SetInitialized:设置资源的初始化状态(标记为已初始化/已反初始化)
- SDL_ShouldInit:检查资源是否需要初始化(防止多线程重复初始化)
- SDL_ShouldQuit:检查是否需要退出(线程退出条件检测)
- SDL_SignalCondition:唤醒一个等待该条件变量的线程(随机唤醒一个)
- SDL_SignalSemaphore:信号量计数加1(释放一个资源许可)
- SDL_TryLockMutex:尝试加锁互斥锁(非阻塞式,获取不到锁直接返回失败)
- SDL_TryLockRWLockForReading:尝试为读操作加读写锁(非阻塞式)
- SDL_TryLockRWLockForWriting:尝试为写操作加读写锁(非阻塞式)
- SDL_TryWaitSemaphore:尝试等待信号量(非阻塞式,计数不足则直接返回失败)
- SDL_UnlockMutex:解锁互斥锁(释放锁,允许其他线程获取)
- SDL_UnlockRWLock:解锁读写锁(释放读/写锁)
- SDL_WaitCondition:等待条件变量(需配合互斥锁使用,阻塞直到被唤醒)
- SDL_WaitConditionTimeout:带超时的条件变量等待(超时后自动返回)
- SDL_WaitSemaphore:等待信号量(阻塞式,计数减1,计数为0则等待)
- SDL_WaitSemaphoreTimeout:带超时的信号量等待(超时后自动返回)
数据类型
- SDL_Condition:条件变量句柄类型(标识一个创建的条件变量)
- SDL_Mutex:互斥锁句柄类型(标识一个创建的互斥锁)
- SDL_RWLock:读写锁句柄类型(标识一个创建的读写锁)
- SDL_Semaphore:信号量句柄类型(标识一个创建的信号量)
结构体
- SDL_InitState:资源初始化状态结构体(用于多线程安全的资源初始化控制)
枚举
- SDL_InitStatus:初始化状态枚举(未初始化/初始化中/已初始化/反初始化中)
宏
- SDL_ACQUIRE:标记获取独占锁的函数(线程安全分析用)
- SDL_ACQUIRE_SHARED:标记获取共享锁的函数(线程安全分析用)
- SDL_ACQUIRED_AFTER:标记锁的获取顺序(当前函数获取的锁在指定锁之后)
- SDL_ACQUIRED_BEFORE:标记锁的获取顺序(当前函数获取的锁在指定锁之前)
- SDL_ASSERT_CAPABILITY:断言当前线程持有指定的独占锁(调试用)
- SDL_ASSERT_SHARED_CAPABILITY:断言当前线程持有指定的共享锁(调试用)
- SDL_CAPABILITY:定义线程安全能力(锁)的类型别名(线程安全分析用)
- SDL_EXCLUDES:标记变量/函数排除指定锁(线程安全分析用)
- SDL_GUARDED_BY:标记变量受指定独占锁保护(线程安全分析用)
- SDL_NO_THREAD_SAFETY_ANALYSIS:禁用当前函数的线程安全分析
- SDL_PT_GUARDED_BY:标记指针指向的变量受指定独占锁保护(线程安全分析用)
- SDL_RELEASE:标记释放独占锁的函数(线程安全分析用)
- SDL_RELEASE_GENERIC:标记释放通用锁的函数(线程安全分析用)
- SDL_RELEASE_SHARED:标记释放共享锁的函数(线程安全分析用)
- SDL_REQUIRES:标记函数要求调用线程持有指定独占锁(线程安全分析用)
- SDL_REQUIRES_SHARED:标记函数要求调用线程持有指定共享锁(线程安全分析用)
- SDL_RETURN_CAPABILITY:标记函数返回一个锁(线程安全分析用)
- SDL_SCOPED_CAPABILITY:标记作用域锁(自动释放锁,线程安全分析用)
- SDL_THREAD_ANNOTATION_ATTRIBUTE__:线程注解属性的基础宏(内部使用)
- SDL_TRY_ACQUIRE:标记尝试获取独占锁的函数(线程安全分析用)
- SDL_TRY_ACQUIRE_SHARED:标记尝试获取共享锁的函数(线程安全分析用)
FreeBASIC 示例代码
' 引入 SDL 相关声明(需确保 FreeBASIC 已链接 SDL3 库)
#Include "SDL.bi"
' 全局共享资源
Dim Shared As Integer g_sharedCounter = 0
' 同步原语
Dim Shared As SDL_Mutex Ptr g_mutex = NULL ' 互斥锁
Dim Shared As SDL_RWLock Ptr g_rwLock = NULL ' 读写锁
Dim Shared As SDL_Semaphore Ptr g_semaphore = NULL ' 信号量
Dim Shared As SDL_Condition Ptr g_condition = NULL ' 条件变量
Dim Shared As SDL_Mutex Ptr g_condMutex = NULL ' 条件变量配套互斥锁
Dim Shared As SDL_InitState g_initState ' 初始化状态
' 线程控制标志
Dim Shared As Boolean g_quitThreads = False
' 初始化状态的辅助函数声明(FreeBASIC 绑定可能缺失)
Declare Function SDL_ShouldInit CDecl (ByVal state As SDL_InitState Ptr) As Integer
Declare Sub SDL_SetInitialized CDecl (ByVal state As SDL_InitState Ptr, ByVal status As SDL_InitStatus)
' 线程1:使用互斥锁访问共享计数器
Function MutexThread 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_LockMutex(g_mutex)
' 安全访问共享资源
g_sharedCounter += 1
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s - 互斥锁:计数器 = %d", threadName, g_sharedCounter)
' 解锁互斥锁
SDL_UnlockMutex(g_mutex)
' 模拟工作
SDL_Delay(300)
Wend
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s (ID: %d) 退出", threadName, threadId)
Return 0
End Function
' 线程2:使用读写锁读取共享计数器(读线程)
Function ReadRWLockThread 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_LockRWLockForReading(g_rwLock)
' 安全读取共享资源
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s - 读锁:计数器 = %d", threadName, g_sharedCounter)
' 解锁读写锁
SDL_UnlockRWLock(g_rwLock)
' 模拟工作
SDL_Delay(500)
Wend
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s (ID: %d) 退出", threadName, threadId)
Return 0
End Function
' 线程3:使用读写锁修改共享计数器(写线程)
Function WriteRWLockThread 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_LockRWLockForWriting(g_rwLock)
' 安全修改共享资源
g_sharedCounter *= 2
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s - 写锁:计数器 ×2 = %d", threadName, g_sharedCounter)
' 解锁读写锁
SDL_UnlockRWLock(g_rwLock)
' 模拟工作
SDL_Delay(2000)
Wend
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s (ID: %d) 退出", threadName, threadId)
Return 0
End Function
' 线程4:使用信号量控制并发
Function SemaphoreThread 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)
' 等待信号量(获取资源许可,最多2个线程同时执行)
SDL_WaitSemaphore(g_semaphore)
' 临界区(最多2个线程同时进入)
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s - 信号量:获取许可,当前计数 = %d", threadName, SDL_GetSemaphoreValue(g_semaphore))
SDL_Delay(1000) ' 模拟耗时操作
' 释放信号量(归还资源许可)
SDL_SignalSemaphore(g_semaphore)
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s - 信号量:释放许可,当前计数 = %d", threadName, SDL_GetSemaphoreValue(g_semaphore))
' 模拟工作
SDL_Delay(500)
Wend
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s (ID: %d) 退出", threadName, threadId)
Return 0
End Function
' 线程5:使用条件变量等待通知
Function ConditionThread 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)
' 加锁条件变量配套的互斥锁
SDL_LockMutex(g_condMutex)
' 等待条件变量(自动释放锁,被唤醒后重新获取锁)
SDL_WaitCondition(g_condition, g_condMutex)
' 被唤醒后执行
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s (ID: %d) 收到通知,计数器 = %d", threadName, threadId, g_sharedCounter)
' 解锁互斥锁
SDL_UnlockMutex(g_condMutex)
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "%s (ID: %d) 退出", threadName, threadId)
Return 0
End Function
' 初始化共享资源(线程安全)
Sub InitSharedResource()
' 确保只有一个线程执行初始化
If (SDL_ShouldInit(@g_initState)) Then
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "主线程:初始化共享资源...")
' 初始化同步原语
g_mutex = SDL_CreateMutex()
g_rwLock = SDL_CreateRWLock()
g_semaphore = SDL_CreateSemaphore(2) ' 初始计数2,最多2个线程并发
g_condition = SDL_CreateCondition()
g_condMutex = SDL_CreateMutex()
' 标记初始化完成
SDL_SetInitialized(@g_initState, SDL_INIT_STATUS_INITIALIZED)
SDL_LogInfo(SDL_LOG_CATEGORY_THREAD, "主线程:共享资源初始化完成")
End If
End Sub
' 主程序
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
' 初始化共享资源(线程安全)
InitSharedResource()
' 检查初始化是否成功
If (g_mutex = NULL Or g_rwLock = NULL Or g_semaphore = NULL Or g_condition = NULL Or g_condMutex = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_THREAD, "同步原语创建失败:%s", SDL_GetError())
g_quitThreads = True
SDL_Quit()
Exit Sub
End If
' 创建线程
Dim As String t1Name = "互斥锁线程"
Dim As String t2Name = "读锁线程1"
Dim As String t3Name = "写锁线程"
Dim As String t4Name = "信号量线程1"
Dim As String t5Name = "信号量线程2"
Dim As String t6Name = "信号量线程3"
Dim As String t7Name = "条件变量线程"
Dim As SDL_Thread Ptr t1 = SDL_CreateThread(@MutexThread, t1Name, StrPtr(t1Name))
Dim As SDL_Thread Ptr t2 = SDL_CreateThread(@ReadRWLockThread, t2Name, StrPtr(t2Name))
Dim As SDL_Thread Ptr t3 = SDL_CreateThread(@WriteRWLockThread, t3Name, StrPtr(t3Name))
Dim As SDL_Thread Ptr t4 = SDL_CreateThread(@SemaphoreThread, t4Name, StrPtr(t4Name))
Dim As SDL_Thread Ptr t5 = SDL_CreateThread(@SemaphoreThread, t5Name, StrPtr(t5Name))
Dim As SDL_Thread Ptr t6 = SDL_CreateThread(@SemaphoreThread, t6Name, StrPtr(t6Name))
Dim As SDL_Thread Ptr t7 = SDL_CreateThread(@ConditionThread, t7Name, StrPtr(t7Name))
If (t1 = NULL Or t2 = NULL Or t3 = NULL Or t4 = NULL Or t5 = NULL Or t6 = NULL Or t7 = NULL) Then
SDL_LogError(SDL_LOG_CATEGORY_THREAD, "创建线程失败:%s", SDL_GetError())
g_quitThreads = True
SDL_Quit()
Exit Sub
End If
' 主线程运行 5 秒
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "主线程运行 5 秒...")
SDL_Delay(5000)
' 发送条件变量通知
SDL_LockMutex(g_condMutex)
SDL_SignalCondition(g_condition) ' 唤醒等待条件变量的线程
SDL_UnlockMutex(g_condMutex)
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "主线程:发送条件变量通知")
' 通知所有线程退出
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_WaitThread(t6, @result)
SDL_WaitThread(t7, @result)
' 清理资源
SDL_DestroyMutex(g_mutex)
SDL_DestroyRWLock(g_rwLock)
SDL_DestroySemaphore(g_semaphore)
SDL_DestroyCondition(g_condition)
SDL_DestroyMutex(g_condMutex)
' 退出 SDL
SDL_Quit()
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "程序正常退出,最终计数器值:%d", g_sharedCounter)
End Sub
' 运行主程序
Main()
核心知识点补充
-
同步原语适用场景:
- 互斥锁(Mutex):最简单的独占锁,适用于所有需要独占访问的场景(如简单的计数器修改);
- 读写锁(RWLock):读多写少的场景(如配置文件读取/更新),读操作共享,写操作独占,提升并发效率;
- 信号量(Semaphore):控制并发数量的场景(如线程池、资源池),计数值表示可用资源数;
- 条件变量(Condition):线程间等待/通知的场景(如生产者-消费者模型),必须配合互斥锁使用。
-
关键使用规则:
- 互斥锁/读写锁:加锁和解锁必须成对出现,且在同一个线程中执行;
- 条件变量:
SDL_WaitCondition()会自动释放传入的互斥锁,被唤醒后重新获取锁; - 信号量:
SDL_WaitSemaphore()使计数减1(获取资源),SDL_SignalSemaphore()使计数加1(释放资源),计数为0时Wait会阻塞; - SDL_InitState:
SDL_ShouldInit()确保只有一个线程执行初始化,避免多线程重复初始化资源。
-
非阻塞操作:
SDL_TryLockMutex()/SDL_TryLockRWLockForReading()/SDL_TryLockRWLockForWriting():非阻塞加锁,获取不到锁时直接返回失败,适合不想阻塞的场景;SDL_TryWaitSemaphore():非阻塞等待信号量,计数不足时直接返回失败。
-
超时操作:
SDL_WaitConditionTimeout():带超时的条件变量等待,超时时间单位为毫秒;SDL_WaitSemaphoreTimeout():带超时的信号量等待,避免线程永久阻塞。
总结
-
核心优势:
- 提供跨平台统一的线程同步接口,屏蔽不同系统的同步原语差异;
- 涵盖从基础互斥锁到高级条件变量的完整同步方案,满足不同场景需求;
SDL_InitState简化多线程安全的资源初始化逻辑。
-
使用建议:
- 优先选择最贴合场景的同步原语(读多写少用读写锁,并发控制用信号量);
- 加锁/解锁必须成对,避免死锁(如忘记解锁、解锁非当前线程持有的锁);
- 条件变量必须配合互斥锁使用,且等待前需加锁;
- 信号量计数值需合理设置,避免资源耗尽或过度并发。
-
关键接口:
- 互斥锁:
SDL_CreateMutex()/SDL_LockMutex()/SDL_UnlockMutex(); - 读写锁:
SDL_CreateRWLock()/SDL_LockRWLockForReading()/SDL_LockRWLockForWriting()/SDL_UnlockRWLock(); - 信号量:
SDL_CreateSemaphore()/SDL_WaitSemaphore()/SDL_SignalSemaphore(); - 条件变量:
SDL_CreateCondition()/SDL_WaitCondition()/SDL_SignalCondition()/SDL_BroadcastCondition(); - 初始化控制:
SDL_ShouldInit()/SDL_SetInitialized()。
- 互斥锁:
评论一下?