DispatchGroup.leave()跨线程执行是否安全?其他并发原语呢?
DispatchGroup().leave() 在不同线程中的正确工作机制
问题背景
之前了解到NSLock的跨线程释放锁存在限制:
NSLock类基于POSIX线程实现锁定逻辑。向NSLock对象发送unlock消息时,必须确保该消息来自发送初始lock消息的同一线程。从不同线程解锁会导致未定义行为。
那NSRecursiveLock、DispatchGroup这类其他并发原语会不会受到同样的限制?
比如我编写的以下代码,wait在主线程执行,leave在后台线程调用,并没有出现因leave通知丢失导致的死锁问题:
func parallelDownload(urls: [URL]) -> [Data] { let session = URLSession.shared var fetchedImages = [Data]() let group = DispatchGroup() for url in urls { let request = URLRequest(url: url) let task = session.dataTask(with: request) { maybeData, maybeResponse, maybeError in defer { print("Leave in completion handler \(Thread.current): Is main thread? \(Thread.current.isMainThread)") group.leave() } // leave after block completes guard maybeError == nil, let response = maybeResponse as? HTTPURLResponse, let data = maybeData else { return } if !(200...299 ~= response.statusCode) { return } fetchedImages.append(data) } group.enter() task.resume() } // Waiting for last task to finish print("Before wait \(Thread.current): Is main thread? \(Thread.current.isMainThread)") group.wait() print("After wait \(Thread.current): Is main thread? \(Thread.current.isMainThread)") return fetchedImages } let urls = Array(repeating: "https://picsum.photos/200/300", count: 3).compactMap { URL(string: $0) } let startTime = Date() let images = parallelDownload(urls: urls) let endTime = Date() print("Download time \(endTime.timeIntervalSinceNow - startTime.timeIntervalSinceNow)")
执行输出:
Before wait <_NSMainThread: 0x600002494180>{number = 1, name = main}: Is main thread? true Leave in completion handler <NSThread: 0x60000249a540>{number = 5, name = (null)}: Is main thread? false Leave in completion handler <NSThread: 0x600002491000>{number = 4, name = (null)}: Is main thread? false Leave in completion handler <NSThread: 0x600002496880>{number = 8, name = (null)}: Is main thread? false After wait <_NSMainThread: 0x600002494180>{number = 1, name = main}: Is main thread? true Download time 0.6723159551620483
解答
核心结论
DispatchGroup 完全支持跨线程调用leave,这和NSLock的限制本质不同,你的代码是合法且安全的。
具体分析
设计定位与实现逻辑差异
- NSLock是互斥锁,核心作用是保护临界区,要求
lock和unlock同线程是为了避免锁状态混乱:如果线程A加锁,线程B解锁,会导致锁被意外释放,其他线程可能错误进入临界区,破坏数据一致性。 - DispatchGroup是任务同步原语,内部维护一个原子性计数:每调用一次
enter计数+1,每调用一次leave计数-1。当计数归0时,触发wait的线程恢复执行,或者执行notify绑定的闭包。这个计数操作是线程安全的,和调用线程无关。
- NSLock是互斥锁,核心作用是保护临界区,要求
NSRecursiveLock的限制
NSLock的跨线程限制同样适用于NSRecursiveLock。它是可重入的互斥锁,需要跟踪当前线程的重入次数,跨线程调用unlock会破坏内部计数逻辑,导致未定义行为。你的代码为何正常运行
主线程调用group.wait()阻塞等待,后台线程完成任务后调用group.leave(),每次leave都会原子性地减少DispatchGroup的内部计数。当最后一个leave调用后计数归0,主线程的wait会立即返回,完全符合预期。
额外提示
你的代码中fetchedImages.append(data)存在线程安全问题:多个后台线程同时对同一个数组执行追加操作,会导致数据竞争,可能引发数组崩溃或数据错乱。建议通过DispatchQueue加锁保护,或者使用线程安全的容器。
内容的提问来源于stack exchange,提问作者Dracula
相关产品推荐
相关产品推荐

