作者:Federico Zanetello 翻译:BigNerdCoding,原文链接
首先,如果你对大中枢派发(GCD)和派发队列不够熟悉的话,请先看 AppCoda 的这篇文章。
在了解了 GCD 内容后,接下来我们来看看 Swift 中的信号机制。
简介
先让我们假象一个场景:有一群作者在写作的时候必须共享一支笔来完成个人的工作。很明显在这种情形下,每次都只能有一个人能够进行写作。
在代码世界中,上述场景中写作者就相当于线程而笔就是需要共享的资源(例如:文件、变量、某种权限)。
那么问题来了,如何保证这些共享资源的互斥使用?
共享资源的访问控制
对于这些资源的互斥使用问题有人可能会想,只需一个 Bool 类型的 resourceIsAvailable 变量就足够了:
if (resourceIsAvailable) { resourceIsAvailable = false useResource() resourceIsAvailable = true } else { // resource is not available,wait or do something else }
但是在多线程并发的情况下(不考虑优先级),我们是无法得知具体是哪个线程在执行上述代码。
示例
例如,现在有两个线程 threadA、threadB 都要执行上面的代码,并且对资源的使用是互斥的。那么就可能出现以下情形:
threadA 首先执行了条件判断语句,并且得到的资源的访问权限。
但是在执行权限锁定的代码之前( resourceIsAvailable = false),处理器切换到 threadB 并且也执行了条件判断语句。
现在两个线程都有了访问权限,这就导致了很严重的问题。
所以,不使用 GCD 就想完成线程安全代码的编写是一件非常困难的事情。
How Semaphores Work:
简单来说,分为三个步骤:
当你需要使用共享资源的时候,首先给型号机制发送权限请求(request)。
当信号机制对你开启绿灯放行的时候,我们就可以确保当前资源已经能够被我们使用。
当资源使用完毕后,你必须给信号机制发送通知(signal),让它回收权限并再次分派给其他线程。
当共享资源只有一份并且只能被一个线程占有的时候,那么你可以将上面的 request/signal 理解为对资源的 lock/unlock。
幕后的运行机制
The Structure
首先信号机制需要一个信号量来控制访问权限,它的组成如下:
一个计数器 counter 用于标记可用资源数,也就是说它表示了当前还有多少资源还能被线程使用。
一个 FIFO 的线程派发队列,用于处理等待资源访问权限的线程。
Resource Request:wait()
当信号机制接受到请求后,它会先去检查自己的资源计数是否大于 0:
如果大于0,则资源计数减 1 ,并将资源分配给请求者使用。
如果不满足,则将该请求线程放到请求队列的最后。
Resource Release:signal()
当信号机制收到一个使用完毕的释放消息时,他会先去检查请求队列:
如果请求队列里的线程不为空的话,则将队列中的第一个线程移出并将资源分配给该线程。
否则则增加资源计数。
Warning: Busy Waiting
当线程向信号机制请求资源分配但是没有得到满足时,该线程将会被冻结直到成功获取了资源的使用权。
⚠️ 如果该线程是主线程的话,那么整个 App 都将会被冻结失去响应。
信号机制在 Swift 中的使用
说了那么多,下面我们通过代码来更好的理解该机制。
Declaration
信号量结构的声明非常的简单:
let semaphore = DispatchSemaphore(value: 1)
其中的参数 value,表示了可供使用的资源总数。
Resource Request
请求资源分配也非常的简单:
semaphore.wait()
需要注意的是,该信号量并没有给予线程任何物理资,仅仅只是一个使用权限。线程只能在 request 和 release 操作之间对资源进行使用。
一旦线程获得了访问权限,那么我们就可以假定线程一定能够对资源进行正常操作。
Resource Release
在释放资源的时候,我们这样写:
semaphore.signal()
当完成资源释放后,该线程就无法使用该资源了,除非它再次发起使用请求。
Semaphore Playgrounds
与AppCoda 的这篇文章一样,接下来我们看看信号机制的真实使用场景。
因为 Swift Playgrounds 并不能完美支持,所以这里我们使用的是 Xcode Playgrounds。希望 WWDC17 中苹果能够对 Swift Playgrounds 进行功能提升吧。
在下面的 playgrounds 中会创建两个线程并且两者将赋予不同的优先级,然后执行的时候打印十次 emoji。
非信号机制下的情形
import Foundation import PlaygroundSupport let higherPriority = DispatchQueue.global(qos: .userInitiated) let lowerPriority = DispatchQueue.global(qos: .utility) func asyncPrint(queue: DispatchQueue,symbol: String) { queue.async { for i in 0...10 { print(symbol,i) } } } asyncPrint(queue: higherPriority,symbol: " 原文链接:https://www.f2er.com/swift/321515.html