https://www.mikeash.com/pyblog/friday-qa-2015-02-06-locks-thread-safety-and-swift.htmlhtml
https://swift.gg/2018/06/07/friday-qa-2015-02-06-locks-thread-safety-and-swift/python
在 Swift 中有个有趣的现象:它没有与线程相关的语法,也没有明确的互斥锁/锁(mutexes/locks
)概念,甚至 Objective-C 中有的 @synchronized
和原子属性它都没有。幸运的是,苹果系统的 API 能够很是容易地应用到 Swift 中。今天,我会介绍这些 API 的用法以及从 Objective-C 过渡的一些问题,这些灵感都来源于 Cameron Pulsford。swift
锁(lock
)或者互斥锁(mutex
)是一种结构,用来保证一段代码在同一时刻只有一个线程执行。它们一般被用来保证多线程访问同一可变数据结构时的数据一致性。主要有下面几种锁:安全
Blocking locks
):常见的表现形式是当前线程会进入休眠,直到被其余线程释放。Spinlocks
):使用一个循环不断地检查锁是否被释放。若是等待状况不多话这种锁是很是高效的,相反,等待状况很是多的状况下会浪费 CPU 时间。Reader/writer locks
):容许多个读线程同时进入一段代码,但当写线程获取锁时,其余线程(包括读取器)只能等待。这是很是有用的,由于大多数数据结构读取时是线程安全的,但当其余线程边读边写时就不安全了。Recursive locks
):容许单个线程屡次获取相同的锁。非递归锁被同一线程重复获取时可能会致使死锁、崩溃或其余错误行为。苹果提供了一系列不一样的锁 API,下面列出了其中一些:数据结构
pthread_mutex_t
pthread_rwlock_t
dispatch_queue_t
NSOperationQueue
当配置为 serial
时NSLock
OSSpinLock
除此以外,Objective-C 提供了 @synchronized
语法结构,它其实就是封装了 pthread_mutex_t
。与其余 API 不一样的是,@synchronized
并未使用专门的锁对象,它能够将任意 Objective-C 对象视为锁。@synchronized(someObject)
区域会阻止其余 @synchronized(someObject)
区域访问同一对象指针。不一样的 API 有不一样的行为和能力:多线程
pthread_mutex_t
是一个可选择性地配置为递归锁的阻塞锁;pthread_rwlock_t
是一个阻塞读写锁;dispatch_queue_t
能够用做阻塞锁,也能够经过使用 barrier block 配置一个同步队列做为读写锁,还支持异步执行加锁代码;NSOperationQueue
能够用做阻塞锁。与 dispatch_queue_t
同样,支持异步执行加锁代码。NSLock
是 Objective-C 类的阻塞锁,它的同伴类 NSRecursiveLock
是递归锁。OSSpinLock
顾名思义,是一个自旋锁。最后,@synchronized
是一个阻塞递归锁。闭包
注意,pthread_mutex_t
,pthread_rwlock_t
和 OSSpinLock
是值类型,而不是引用类型。这意味着若是你用 =
进行赋值操做,实际上会复制一个副本。这会形成严重的后果,由于这些类型没法复制!若是你不当心复制了它们中的任意一个,这个副本没法使用,若是使用可能会直接崩溃。这些类型的 pthread
函数会假定它们的内存地址与初始化时同样,所以若是将它们移动到其余地方就可能会出问题。OSSpinLock
不会崩溃,但复制操做会生成一个彻底独立的锁,这不是你想要的。并发
若是使用这些类型,就必须注意不要去复制它们,不管是显式的使用 =
操做符仍是隐式地操做。
例如,将它们嵌入到结构中或在闭包中捕获它们。框架
另外,因为锁本质上是可变对象,须要用 var
来声明它们。异步
其余锁都是是引用类型,它们能够随意传递,而且能够用 let
声明。
2015-02-10 更新:本节中所描述的问题已经以惊人的速度被淘汰。苹果昨天发布了 Xcode 6.3 beta 1,其中包括 Swift 1.2。在其余更改中,如今使用一个空的初始化器导入 C 结构,将全部字段设置为零。简而言之,你如今能够直接使用 pthread_mutex_t()
,不须要下面提到的扩展。
pthread 类型很难在 swift 中使用。它们被定义为不透明的结构体中包含了一堆存储变量,例如:
struct _opaque_pthread_mutex_t { |
目的是声明它们,而后使用 init 函数对它们进行初始化,使用一个指针存储和填充。在 C 中,它看起来像:
pthread_mutex_t mutex; |
这段代码能够正常的工做,只要你记得调用 pthread_mutex_init
。然而,Swift 真的真的不喜欢未初始化的变量。与上面代码等效的 Swift 版本没法编译:
var mutex: pthread_mutex_t |
Swift 须要变量在使用前初始化。pthread_mutex_init
不使用传入的变量的值,只是重写它,可是 Swift 不知道,所以它产生了一个错误。为了知足编译器,变量须要用某种东西初始化。在类型以后使用 ()
,但这样写仍然会报错:
var mutex = pthread_mutex_t() |
Swift 须要那些不透明字段的值。__sig
能够传入零,可是 __opaque
就有点烦人了。下面的结构体须要桥接到 swift 中:
struct _opaque_pthread_mutex_t { |
目前没有简单的方法使用一堆 0 构建一个元组,只能像下面这样把全部的 0 都写出来:
var mutex = pthread_mutex_t(__sig: 0, |
这么写太难看了,但我没找到好的方法。我能想到最好的作法就是把它写到一个扩展中,这样直接使用空的 ()
就能够了。下面是我写的两个扩展:
extension pthread_mutex_t { |