swift 锁

https://www.mikeash.com/pyblog/friday-qa-2015-02-06-locks-thread-safety-and-swift.html

https://swift.gg/2018/06/07/friday-qa-2015-02-06-locks-thread-safety-and-swift/

 

在 Swift 中有个有趣的现象:它没有与线程相关的语法,也没有明确的互斥锁/锁(mutexes/locks)概念,甚至 Objective-C 中有的 @synchronized 和原子属性它都没有。幸运的是,苹果系统的 API 可以非常容易地应用到 Swift 中。今天,我会介绍这些 API 的用法以及从 Objective-C 过渡的一些问题,这些灵感都来源于 Cameron Pulsford。

快速回顾一下锁

锁(lock)或者互斥锁(mutex)是一种结构,用来保证一段代码在同一时刻只有一个线程执行。它们通常被用来保证多线程访问同一可变数据结构时的数据一致性。主要有下面几种锁:

  • 阻塞锁(Blocking locks):常见的表现形式是当前线程会进入休眠,直到被其他线程释放。
  • 自旋锁(Spinlocks):使用一个循环不断地检查锁是否被释放。如果等待情况很少话这种锁是非常高效的,相反,等待情况非常多的情况下会浪费 CPU 时间。
  • 读写锁(Reader/writer locks):允许多个读线程同时进入一段代码,但当写线程获取锁时,其他线程(包括读取器)只能等待。这是非常有用的,因为大多数数据结构读取时是线程安全的,但当其他线程边读边写时就不安全了。
  • 递归锁(Recursive locks):允许单个线程多次获取相同的锁。非递归锁被同一线程重复获取时可能会导致死锁、崩溃或其他错误行为。

APIs

苹果提供了一系列不同的锁 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_tpthread_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 {
long __sig;
char __opaque[__PTHREAD_MUTEX_SIZE__];
};

目的是声明它们,然后使用 init 函数对它们进行初始化,使用一个指针存储和填充。在 C 中,它看起来像:

pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);

这段代码可以正常的工作,只要你记得调用 pthread_mutex_init。然而,Swift 真的真的不喜欢未初始化的变量。与上面代码等效的 Swift 版本无法编译:

var mutex: pthread_mutex_t
pthread_mutex_init(&mutex, nil)
// error: address of variable 'mutex' taken before it is initialized

Swift 需要变量在使用前初始化。pthread_mutex_init 不使用传入的变量的值,只是重写它,但是 Swift 不知道,因此它产生了一个错误。为了满足编译器,变量需要用某种东西初始化。在类型之后使用 (),但这样写仍然会报错:

var mutex = pthread_mutex_t()
// error: missing argument for parameter '__sig' in call

Swift 需要那些不透明字段的值。__sig 可以传入零,但是 __opaque 就有点烦人了。下面的结构体需要桥接到 swift 中:

struct _opaque_pthread_mutex_t {
var __sig: Int
var __opaque: (Int8, Int8, Int8, Int8,
Int8, Int8, Int8, Int8,
Int8, Int8, Int8, Int8,
Int8, Int8, Int8, Int8,
Int8, Int8, Int8, Int8,
Int8, Int8, Int8, Int8,
Int8, Int8, Int8, Int8,
Int8, Int8, Int8, Int8,
Int8, Int8, Int8, Int8,
Int8, Int8, Int8, Int8,
Int8, Int8, Int8, Int8,
Int8, Int8, Int8, Int8,
Int8, Int8, Int8, Int8,
Int8, Int8, Int8, Int8)
}

目前没有简单的方法使用一堆 0 构建一个元组,只能像下面这样把所有的 0 都写出来:

var mutex = pthread_mutex_t(__sig: 0,
__opaque: (0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0))

这么写太难看了,但我没找到好的方法。我能想到最好的做法就是把它写到一个扩展中,这样直接使用空的 () 就可以了。下面是我写的两个扩展:

extension pthread_mutex_t {
init() {
__sig = 0
__opaque = (0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0)
}
}

extension pthread_rwlock_t {
init() {
__sig = 0
__opaque = (0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,

转载于:https://www.cnblogs.com/saytome/p/10075093.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值