我的Swift代码需要调用一些非线程安全的C函数。所有电话都需要:
1)同步(函数的顺序调用,仅在前一次调用返回后),
2)在同一个线程上。
我试图创建一个队列,然后从函数中访问C:
let queue = DispatchQueue(label: "com.example.app.thread-1", qos: .userInitiated)
func calc(...) -> Double {
var result: Double!
queue.sync {
result = c_func(...)
}
return result
}
这改善了行为,但我仍然遇到崩溃 - 有时候,不像以前那样频繁,而且主要是在从Xcode调试时。有关更好处理的任何想法?
编辑
根据下面的评论,有人可以给出一个如何使用线程类来确保在同一线程上顺序执行的一般示例吗?
编辑2
当在C库周围使用这个包装器时,可以看到问题的一个很好的例子:https://github.com/PerfectlySoft/Perfect-PostgreSQL
从单个队列访问时,它工作正常。但如果涉及多个调度队列,将开始产生奇怪的错误。
所以我设想一个单个执行程序线程的方法,当被调用时,它会阻塞调用者,执行计算,解除调用者的阻塞并返回结果。对每个连续的呼叫者重复此操作
像这样的东西:
thread 1 | |
---------> | | ---->
thread 2 | executor | ---->
---------> | thread |
thread 3 | -----------> |
---------> | | ---->
...
如果您确实需要确保所有API调用必须来自单个线程,则可以使用Thread
类加上some synchronization primitives来实现。
例如,下面的SingleThreadExecutor
类提供了这种想法的简单实现:
class SingleThreadExecutor {
private var thread: Thread!
private let threadAvailability = DispatchSemaphore(value: 1)
private var nextBlock: (() -> Void)?
private let nextBlockPending = DispatchSemaphore(value: 0)
private let nextBlockDone = DispatchSemaphore(value: 0)
init(label: String) {
thread = Thread(block: self.run)
thread.name = label
thread.start()
}
func sync(block: @escaping () -> Void) {
threadAvailability.wait()
nextBlock = block
nextBlockPending.signal()
nextBlockDone.wait()
nextBlock = nil
threadAvailability.signal()
}
private func run() {
while true {
nextBlockPending.wait()
nextBlock!()
nextBlockDone.signal()
}
}
}
确保指定块的简单测试实际上是由单个线程调用的:
let executor = SingleThreadExecutor(label: "single thread test")
for i in 0..<10 {
DispatchQueue.global().async {
executor.sync { print("\(i) @ \(Thread.current.name!)") }
}
}
Thread.sleep(forTimeInterval: 5) /* Wait for calls to finish. */
0 @ single thread test
1 @ single thread test
2 @ single thread test
3 @ single thread test
4 @ single thread test
5 @ single thread test
6 @ single thread test
7 @ single thread test
8 @ single thread test
9 @ single thread test
最后,在你的代码中用DispatchQueue
替换SingleThreadExecutor
,让我们希望这可以修复你的 - 非常异国情调! - 问题 ;)
let singleThreadExecutor = SingleThreadExecutor(label: "com.example.app.thread-1")
func calc(...) -> Double {
var result: Double!
singleThreadExecutor.sync {
result = c_func(...)
}
return result
}
一个有趣的结果......我对Paulo Mattos的解决方案的性能进行了基准测试,我已经接受了我之前的实验,其中我使用了一个不那么优雅和较低级别的运行循环和对象引用方法来实现相同的模式。
游乐场以封闭为基础的方法:https://gist.github.com/deze333/23d11123f02e65c456d16ffe5621e2ee
运行循环和参考传递方法的游乐场:https://gist.github.com/deze333/82c0ee3e82fd250097449b1b200b7958
使用闭包:
Invocations processed : 1000
Invocations duration, sec: 4.95894199609756
Cost per invocation, sec : 0.00495894199609756
使用run循环并传递对象引用:
Invocations processed : 1000
Invocations duration, sec: 1.62595099210739
Cost per invocation, sec : 0.00162432666544195
传递闭包的速度是x3倍,因为它们是在堆上与参考传递分配的。这确实证实了优秀的Mutexes and closure capture in Swift文章中概述的封闭性能问题。
经验教训:当需要最大性能时,不要过度使用闭包,这在移动开发中通常就是这种情况。
闭包是如此美丽!
编辑:
通过整个模块优化,Swift 4中的情况要好得多。关闭速度很快!