Swift 3 GCDlockingvariables和block_and_release错误

我正在使用Swift 3 GCD来执行我的代码中的一些操作。 但是我经常遇到_dispatch_call_block_and_release错误。 我想这个错误背后的原因是因为不同的线程修改相同的variables,但我不知道如何解决问题。 这是我的代码和解释:

我有一个variables在不同的线程中被访问和修改:

var queueMsgSent: Dictionary<Date,BTCommand>? = nil func lock(obj: AnyObject, blk:() -> ()) { objc_sync_enter(obj) blk() objc_sync_exit(obj) } func addMsgSentToQueue(msg: BTCommands) { if queueMsgSent == nil { queueMsgSent = Dictionary.init() } let currentDate = Date() lock(obj: queueMsgSent as AnyObject) { queueMsgSent?.updateValue(msg, forKey: currentDate) } } func deleteMsgSentWithId(id: Int) { if queueMsgSent == nil { return } for (date, msg) in queueMsgSent! { if msg.isAck() == false && msg.getId()! == id { lock(obj: queueMsgSent as AnyObject) { queueMsgSent?.removeValue(forKey: date) } } } } func runSent() -> Void { while(true) { if queueMsgSent == nil { continue } for (date, msg) in queueMsgSent! { if msg.isSent() == false { mainSearchView?.btCom?.write(str: msg.getCommand()!) msg.setSent(val: true) lastMsgSent = Date() continue } if msg.isAck() == true { lock(obj: queueMsgSent as AnyObject) { queueMsgSent?.removeValue(forKey: date) } continue } } } } 

我开始runSent方法为:

  DispatchQueue.global().async(execute: runSent) 

我需要runSent不断地检查一下在queueMsgSent中的一些条件,其他函数addMsgSentToQueueue和deleteMsgSentWithId在主线程ID中被调用。 我正在使用一些locking机制,但其工作不正常

我强烈build议您使用Grand Central Dispatch提供的DispatchQueue ,这使得multithreadingpipe理变得更容易。

命令

让我们从你的命令类开始

 class Command { let id: String var isAck = false var isSent = false init(id:String) { self.id = id } } 

队列

现在我们可以构build我们的Queue类,它将提供以下function

这是我们的类不应该混淆DispatchQueue的概念!

  1. Command推入队列
  2. 从队列中删除一个Command
  3. 开始处理所有元素到队列中

现在的代码是:

 class Queue { typealias Element = (date:Date, command:Command) private var storage: [Element] = [] private let serialQueue = DispatchQueue(label: "serialQueue") func push(command:Command) { serialQueue.async { let newElement = (Date(), command) self.storage.append(newElement) } } func delete(by id: String) { serialQueue.async { guard let index = self.storage.index(where: { $0.command.id == id }) else { return } self.storage.remove(at: index) } } func startProcessing() { Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in self.processElements() } } private func processElements() { serialQueue.async { // send messages where isSent == false let shouldBeSent = self.storage.filter { !$0.command.isSent } for elm in shouldBeSent { // TODO: add here code to send message elm.command.isSent = true } // remove from storage message where isAck == true self.storage = self.storage.filter { !$0.command.isAck } } } } 

它是如何工作的?

正如你所看到的, storage属性是一个拥有元组列表的数组,每个元组有两个组件: DateCommand

由于storagearrays是通过多个线程访问的,因此我们需要确保以线程安全的方式访问storagearrays。

所以每次我们访问storage我们都把代码包装进去

 serialQueue.async { // access self.storage safely } 

我们写入上面的闭包Each中的每个代码都被添加到我们的串行调度队列中

串行队列当时处理1closures。 这就是为什么我们的存储属性以线程安全的方式访问!

在这里输入图像说明

最后的考虑

下面的代码块是邪恶的

 while true { ... } 

它确实使用了所有可用的CPU时间,它冻结了UI(当在主线程上执行时)并放电。

正如你所看到的,我用它来代替它

 Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in self.processElements() } 

它每10秒钟调用self.processElements() ,给CPU处理其他线程留下足够的时间。

当然,这取决于你改变秒数,以更好地适应你的情况。

如果你对objc机制感到不舒服,你可以看看这里 。 使用它,你为你想要协调的特定同步创build一个PThreadMutex,然后使用mutex.fastsync{ *your code* }来隔离访问。 这是一个使用操作系统级调用的简单,非常轻量级的机制,但您必须小心创build死锁。

您提供的示例取决于对象始终是相同的物理实体,因为objc锁使用地址作为正在同步的ID。 因为你似乎要检查queueMsgSent是否存在,我想知道更新值例程在做什么 – 如果它删除了字典,希望稍后创build它,你将有一个潜在的竞争作为不同的线程可以看着不同的同步器。

另外,你在runSent中的循环是一个旋转循环 – 如果没有什么可做的事,它只会烧掉CPU而不是等待工作。 也许你可以考虑修改这个来使用信号量或者一些更合适的机制,让工人们在什么都不做的时候阻止呢?