如何防止Swift函数同时多次执行?确保串行完成一个调用再执行下一个
保证日志操作串行执行的几种实用方案
你的write函数可能被多线程同时调用,要确保前一次的文件追加和日志发送完成后再执行下一次,下面是几种靠谱的实现方式:
1. 串行DispatchQueue(最常用)
在你的类里定义一个串行队列,把所有日志操作都丢到这个队列里执行——串行队列的特性就是任务会按顺序一个接一个跑,完全不会并行。
代码实现:
// 在类内部定义串行队列 private let serialLogQueue = DispatchQueue(label: "com.yourapp.log.serial") func write(_ text: String) { // 把操作异步提交到串行队列 serialLogQueue.async { [weak self] in guard let self = self else { return } // 这里的代码会严格按调用顺序执行 text.appendLineToURL(fileURL: self.path) self.sendLog(text) } }
为什么用async?因为这样不会阻塞调用write的线程(比如主线程),避免UI卡顿。如果你需要等待当前日志操作完成再做后续动作,也可以换成sync,但一般异步更实用。
2. DispatchSemaphore(信号量互斥)
用信号量来做互斥控制,相当于给write加了一把"锁",同一时间只有一个线程能执行里面的代码。
代码实现:
// 初始化信号量,初始值为1(代表允许1个线程进入临界区) private let logSemaphore = DispatchSemaphore(value: 1) func write(_ text: String) { // 等待信号量,获取"锁" logSemaphore.wait() // defer保证不管代码怎么退出,都会释放信号量 defer { logSemaphore.signal() } text.appendLineToURL(fileURL: self.path) sendLog(text) }
注意:如果在同一个线程里递归调用write,会导致死锁——因为当前线程已经拿到了信号量,再次wait会一直等自己释放,永远卡着。所以这种方式要避免递归调用的场景。
3. 串行OperationQueue(灵活任务管理)
如果后续需要对日志任务做更灵活的控制(比如取消未执行的任务、添加任务依赖),可以用OperationQueue,把最大并发数设为1就变成了串行队列。
代码实现:
private let logOperationQueue = OperationQueue() // 类初始化时配置队列 init() { logOperationQueue.maxConcurrentOperationCount = 1 logOperationQueue.name = "com.yourapp.log.operation" } func write(_ text: String) { // 创建一个BlockOperation包装日志操作 let logOperation = BlockOperation { [weak self] in guard let self = self else { return } text.appendLineToURL(fileURL: self.path) self.sendLog(text) } // 添加到串行队列执行 logOperationQueue.addOperation(logOperation) }
这种方式和串行队列本质类似,但Operation支持更多高级特性,适合复杂的任务场景。
总结
- 大多数情况优先选串行DispatchQueue,简单、高效、无坑;
- 需要任务管理灵活性选OperationQueue;
- 信号量适合简单互斥,但要注意死锁风险。
内容的提问来源于stack exchange,提问作者Ginso
相关产品推荐
相关产品推荐

