为什么我明明设置了c 11 递归锁锁,但是锁的时候还是死锁了

76被浏览4,527分享邀请回答public class Child extends Father
public static void main(String[] args)
Child child = new Child();
child.doSomething();
public synchronized void doSomething()
System.out.println("child.doSomething()");
doAnotherThing(); // 调用自己类中其他的synchronized
private synchronized void doAnotherThing()
super.doSomething(); // 调用父类的synchronized
System.out.println("child.doAnotherThing()");
class Father
public synchronized void doSomething()
System.out.println("father.doSomething()");
child.doSomething()
father.doSomething()
child.doAnotherThing()
import java.util.concurrent.locks.L
import java.util.concurrent.locks.ReentrantL
public class Child extends Father
public static void main(String[] args)
Child child = new Child();
child.doSomething();
public void doSomething()
lock.lock();
System.out.println("child.doSomething()");
doAnotherThing(); // 调用自己类中其他的synchronized
lock.unlock();
private void doAnotherThing()
lock.lock();
super.doSomething(); // 调用父类的synchronized
System.out.println("child.doAnotherThing()");
lock.unlock();
class Father
Lock lock = new ReentrantLock();
public void doSomething()
lock.lock();
System.out.println("father.doSomething()");
lock.unlock();
child.doSomething()
father.doSomething()
child.doAnotherThing()
无论是父类还是自己类内部的锁,都是可以重入的,只要是同一线程内部。再比如public class Test implements Runnable
public synchronized void get()
System.out.println(Thread.currentThread().getId());
public synchronized void set()
System.out.println(Thread.currentThread().getId());
public void run()
public static void main(String[] args)
Test ss = new Test();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
88991010import java.util.concurrent.locks.ReentrantL
public class Test implements Runnable
ReentrantLock lock = new ReentrantLock();
public void get()
lock.lock();
System.out.println(Thread.currentThread().getId());
lock.unlock();
public void set()
lock.lock();
System.out.println(Thread.currentThread().getId());
lock.unlock();
public void run()
public static void main(String[] args)
Test ss = new Test();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
88101099-----------------------------------------文字上面总结就是Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
五、以上规则对其它对象锁同样适用.31添加评论分享收藏感谢收起public void lock() {
// step 1. try to change a atomic state
boolean ok = state.compareAndSet(0, 1);
// step 2. set exclusive thread if ok
setExclusiveThread(Thread.current()); // 这只是个标志位,不用太介意
// step 3. enqueue
enqueue();
// step 4. block
Unsafe.park();
// step 5. retry
小朋友们不要轻易模仿。没有谁用这种傻逼的递归写法的,除了我。完整的代码比这个复杂,除了基本的流程,还要处理是否是公平锁,处理线程中断,以及一系列的无锁数据结构等等。几个要点:通过一个原子状态来控制谁进入临界区通过一个链表队列,记录等待获取锁的线程通过Unsafe的park()函数,来把当前线程的运行状态设置成挂起,并且停止调度当已经获取锁的线程调用unlock()函数的时候,就会使用Unsafe.unpark()函数来唤醒等待队列头部的线程唤醒之后,线程继续试着获取锁,失败则递归,成功则返回慢着,知道上面的东西,离我们证明题干还有一定的距离,继续看。Tips: 整个concurrent包源自于JSR-166,其作者就是大名鼎鼎的,说他是这个世界上对Java影响力最大的个人,一点也不为过。因为两次Java历史上的大变革,他都间接或直接的扮演了举足轻重的角色。一次是由JDK 1.1到JDK 1.2,JDK1.2很重要的一项新创举就是Collections,其Collections的概念可以说承袭自Doug Lea于1995年发布的第一个被广泛应用的collections;一次是2004年所推出的Tiger。Tiger广纳了15项JSRs(Java Specification Requests)的语法及标准,其中一项便是JSR-166就是这个小朋友,归纳总结出,嗯各种同步手段底层都需要一些共同的东西,所以写了一个类叫java.util.concurrent.locks.AbstractQueuedSynchronizer。后来被简称为AQS框架,该框架将加锁的步骤模板化了之后,提供了基本的列表、状态控制等等手段。我们可以简单看看lock的过程他是如何抽象的: public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
一共四步:tryAcquire,抽象方法,由子类实现,子类通过控制原子变量来表示是否获取锁成功,类似于上文代码的Step1、Step2addWaiter,已经实现的方法,表示将当前线程加入等待队列,类似于上文的Step3acquireQueued(),挂起线程,唤醒后重试,类似于上文的Step4、Step5处理线程中断标志位。我们只需要记住一个重要的地方就是,子类只需要实现tryAcquire方法,就可以实现一个锁,嗯,不错!而这个tryAcquire方法最重要的就是利用AQS类中提供的原子操作来控制状态。我们看一个最简单的Mutex的例子: public boolean tryAcquire(int acquires) {
assert acquires == 1; // Otherwise unused
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
简单解释一下,compareAndSetState是父类AQS中提供的protected方法,setExclusiveOwnerThread同理。如此我们就实现了一个简单的Mutex。现在我们考虑一个问题,这个基于AQS实现的Mutex是不是可重入的呢?当然不是,线程A调用lock方法,然后就调用到这个tryAcquire函数中,显然这个状态就是被设置成了1。线程A第二次进来的时候,再次控制这个原子变量,发现就不好使了,就进入等待队列。自己就被自己等死了。好,最后就是重点,ReentrantLock也是在AQS的基础上实现的,那么我们来看,他的tryAcquire方法是怎么写的。简单起见,ReentrantLock有公平和非公平的两种实现,我们只关注可重入的特点,这里就不介绍,我们直接看非公平的版本。final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc & 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
return false;
我来解释下这段代码:如果当前的state(AQS提供的原子变量)=0,意味着没有人占用,那么我们compareAndSet来占用,并且设置自己为独占线程如果独占线程就是当前线程,那么说明就是我自己锁住啦(可重入),那么把state计数累加。貌似这样就说通了。还有一个点就是不要小看这个累加哦,在unlock的时候也是一个累减的过程,也就是同一个线程针对同一个ReentrantLock对象调用了10次lock操作,那么对应的,就需要调用10次unlock操作。才会真正的释放lock。我想差不多应该可以证明了吧..对这个类比较感兴趣的小朋友可以参考爸爸的两篇博客:、。然后现在已经晚上10点了,爸爸要回家睡觉了。同步块的部分以后想起了再更吧。那不过是用c艹实现的版本,原理一致,代码几乎也差不多。178 条评论分享收藏感谢收起线程同步之利器(1)——可递归锁与非递归锁
我的图书馆
线程同步之利器(1)——可递归锁与非递归锁
&&&为了接口的将来的扩展性,可以将bar()函数用同样方法拆成bar_withou_lock()函数和bar()函数。
&&&在Douglas C. Schmidt(框架的主要编写者)的“”论文中,提出了一个基于C++的线程安全接口模式(Thread-safe interface pattern),与AUPE的方法有异曲同工之妙。即在设计接口的时候,每个函数也被拆成两个函数,没有使用锁的函数是private或者protected类型,使用锁的的函数是public类型。接口如下:
foo(); //加锁
bar(); //加锁
foo_nolock();
bar_nolock();
作为对外接口的public函数只能调用无锁的私有变量函数,而不能互相调用。在函数具体实现上,这两种方法基本是一样的。
&&&上面讲的两种方法在通常情况下是没问题的,可以有效的避免死锁。但是有些复杂的回调情况下,则必须使用递归锁。比如foo函数调用了外部库的函数,而外部库的函数又回调了bar()函数,此时必须使用递归锁,否则仍然会死锁。AUPE 一书在第十二章就举了一个必须使用递归锁的程序例子。1.3 读写锁的递归性
&&& 读写锁(例如Linux中的pthread_rwlock_t)提供了一个比互斥锁更高级别的并发访问。读写锁的实现往往是比互斥锁要复杂的,因此开销通常也大于互斥锁。在我的Linux机器上实验发现,单纯的写锁的时间开销差不多是互斥锁十倍左右。
在系统不支持读写锁时,有时需要自己来实现,通常是用条件变量加读写计数器实现的。有时可以根据实际情况,实现读者优先或者写者优先的读写锁。
&&&读写锁的优势往往展现在读操作很频繁,而写操作较少的情况下。如果写操作的次数多于读操作,并且写操作的时间都很短,则程序很大部分的开销都花在了读写锁上,这时反而用互斥锁效率会更高些。
&& 相信很多同学学习了读写锁的基本使用方法后,都写过下面这样的程序(Linux下实现)。
#include &pthread.h&
int main()
pthread_rwlock_
pthread_rwlock_rdlock(&rwl);
pthread_rwlock_wrlock(&rwl);
pthread_rwlock_unlock(&rwl);
pthread_rwlock_unlock(&rwl);
return -1;
#include &pthread.h&
int main()
pthread_rwlock_
pthread_rwlock_wrlock(&rwl);
pthread_rwlock_rdlock(&rwl);
pthread_rwlock_unlock(&rwl);
pthread_rwlock_unlock(&rwl);
return -1;
&&& 你会很疑惑的发现,程序1先加读锁,后加写锁,按理来说应该阻塞,但程序却能顺利执行。而程序2却发生了阻塞。&
&&& 更近一步,你能说出执行下面的程序3和程序4会发生什么吗?
#include &pthread.h&
int main()
pthread_rwlock_
pthread_rwlock_rdlock(&rwl);
pthread_rwlock_rdlock(&rwl);
pthread_rwlock_unlock(&rwl);
pthread_rwlock_unlock(&rwl);
return -1;
#include &pthread.h&
int main()
pthread_rwlock_
pthread_rwlock_wrlock(&rwl);
pthread_rwlock_wrlock(&rwl);
pthread_rwlock_unlock(&rwl);
pthread_rwlock_unlock(&rwl);
return -1;
在POSIX标准中,如果一个线程先获得写锁,又获得读锁,则结果是无法预测的。这就是为什么程序1的运行出人所料。需要注意的是,读锁是递归锁(即可重入),写锁是非递归锁(即不可重入)。因此程序3不会死锁,而程序4会一直阻塞。&
读写锁是否可以递归会可能随着平台的不同而不同,因此为了避免混淆,建议在不清楚的情况下尽量避免在同一个线程下混用读锁和写锁。&
&& 在系统不支持递归锁,而又必须要使用时,就需要自己构造一个递归锁。通常,递归锁是在非递归互斥锁加引用计数器来实现的。简单的说,在加锁前,先判断上一个加锁的线程和当前加锁的线程是否为同一个。如果是同一个线程,则仅仅引用计数器加1。如果不是的话,则引用计数器设为1,则记录当前线程号,并加锁。一个例子可以看。需要注意的是,如果自己想写一个递归锁作为公用库使用,就需要考虑更多的异常情况和错误处理,让代码更健壮一些。
TA的最新馆藏[转]&[转]&[转]&[转]&[转]&[转]&
喜欢该文的人也喜欢【翻译】星期五问答 :锁、线程安全与 Swift:2017 版给这些类型写一个扩展(extension)来包装初始化过程是很诱人的。然而这无法保证构造函数直接工作到一个变量而不是一份拷贝上。因为这些类型无法安全的被拷贝,这样的扩展是无法安全的写出来的,除非你返回一个指针或者一个包装类(wrapper class)。如果你使用这些 API,销毁锁时别忘了调用相应的 destory 函数。使用DispatchQueue 有着基于回调(callback-based)的 API,这使得对它的使用有着天然的安全性。取决于你想同步或者异步的执行被保护的代码,调用 sync 或者 async 并传入代码来运行:queue.sync(execute: { ... })
queue.async(execute: { ... })
对于 sync 来说,它提供了一个很棒的特性,将被保护的代码的返回值捕获为 sync 方法的返回值:let value = queue.sync(execute: { return self.protectedProperty })
你甚至可以在被保护的代码中 throw 错误,它会将其传递出来。OperationQueue 很相似,但是没有内建的方法来传出返回值或者错误。你需要自己实现,或者使用 DispatchQueue 来替代。其他的 API 需要单独的加锁和解锁调用,当你忘了调用其中一个时会非常刺激。这些调用长这样:pthread_mutex_lock(&mutex)
pthread_mutex_unlock(&mutex)
nslock.lock()
nslock.unlock()
os_unfair_lock_lock(&lock)
os_unfair_lock_unlock(&lock)
因为这些 API 都一模一样,接下来的例子我只会使用 nslock。其他除了函数名以外都是一样的。当被保护的代码很简单的时候,这可以工作的很好。但是如果情况更复杂呢?举个例子:nslock.lock()
if earlyExitCondition {
return nil
let value = compute()
nslock.unlock()
return value
哎哟,有时候你没有解锁!这是一种写出难以找到的 bug 的方法。也许你总是正确的对待你的 return 语句,绝不会写出这种代码。但如果你要抛出错误呢?nslock.lock()
guard something else { throw error }
let value = compute()
nslock.unlock()
return value
同样的问题!也许你非常的自律并且从不会写这种代码。虽然你的代码是安全的,但是代码有那么一点丑陋:nslock.lock()
let value = compute()
nslock.unlock()
return value
明显的修复方式是使用 Swift 的 defer 语法。在你加锁的时候,defer 接锁。不管你怎么样退出代码,锁一定会被释放:nslock.lock()
defer { nslock.unlock() }
return compute()
这对提早退出、抛错或者正常退出都有效。写这两行代码还是会让人不爽,所以让我们将这些事情包装成一个像 DispatchQueue 那样基于回调的函数:func withLocked&T&(_ lock: NSLock,
_ f: () throws -& T) rethrows -& T {
lock.lock()
defer { lock.unlock() }
return try f()
let value = withLocked(lock, { return self.protectedProperty })
当为值类型实现这个函数的时候,你需要接收一个锁的指针而不是锁本身作为参数。记住,你不会想要拷贝这些玩意!pthread 版本长这样:func withLocked&T&(_ mutexPtr: UnsafeMutablePointer&pthread_mutex_t&,
_ f: () throws -& T) rethrows -& T {
pthread_mutex_lock(mutexPtr)
defer { pthread_mutex_unlock(mutexPtr) }
return try f()
选择你的锁 APIDispatchQueue 显然是最受喜爱的。它有着 Swifty 的 API,用起来也很爽。Apple 对 Dispatch 这个库投入了很多注意,这表明你可以指望它性能良好、运行可靠并且获得一堆炫酷的新功能。DispatchQueue 允许很多有用的进阶用法,比如在被你当作锁的队列上触发安排好的定时器(timers)或者事件源(event sources),保证它们的 handler 和其他使用这个队列的东西同步。设定目标队列(target queues)的能力允许表示复杂的锁层级。自定义并发队列可以很容易的被用作读写锁。你只要改变一个字母就能从同步执行变成异步的在一个后台线程上执行被保护的代码。 并且它的 API 很容易使用,很难被误用。不管在哪方面都很优秀。这都是 GCD 快速变为我最喜欢的 API 之一的原因。像所有的事物一样,它不是完美的。一个 Dispatch 队列是由一个内存中的对象表示的,所以开销有一点点大。它还缺少一些有用的功能,比如条件变量(condition variables)和递归性。总会有一些时候,使用独立的加锁和解锁调用比被强制使用基于回调的 API 更好使。DispatchQueue 通常是正确的选择,同样也是你不知道该选择什么时最棒的默认选项,但偶尔会有一些原因需要使用其他的 API。os_unfair_lock 是一个当每个锁的开销不能太大(因为某些原因你可能有一大堆锁)并且不需要一些华丽功能时的正确选择。它由一个可以放在任何地方的 32 位整数实现,所以开销非常小。像它的名字提示的一样,os_unfair_lock 的一个特性就是没有公平性。锁的公平性表示至少会有企图,保证所有等待锁释放的线程都有机会获得锁。没有公平性的话,一个线程可以在有其他线程等待时通过快速释放和重新获取锁来独占一个锁。这是不是一个问题取决于你在做什么。有一些时候公平性是必要的,有一些时候公平性是没有什么用的。缺少公平性使得 os_unfair_lock 有着更好的性能,使它在公平性不需要的场景更有优势。pthread_mutex 夹在上面两者之间。它比 os_unfair_lock 明显大很多,有 64 字节,但你还是可以控制它存储在哪里。它实现了公平性,尽管这是 Apple 的实现中的细节,而不是 API 标准中写出的。它也提供了各种进阶功能,比如使锁递归的能力和复杂的线程优先级相关的玩意。pthread_rwlock 提供了一种读写锁。它占用巨大的 200 字节但并没有提供什么有趣的功能,所以看起来并没有多少理由使用它而不使用一个并发的 DispatchQueue。NSLock 是对 pthread_mutex 的包装。很难想象出一个它的用例,但当你需要明确的加锁和解锁调用但是不想要 pthread_mutex 那些麻烦的手动初始化和销毁时它会很有用。OperationQueue 提供了和 DispatchQueue 相似的基于回调的 API,还有一下进阶功能比如 operation 间的依赖管理,但是缺少很多其他 DispatchQueue 提供的功能。尽管它在做别的事情时很有用,但 OperationQueue 没有什么原因被用作加锁的 API。总之:DispatchQueue 很可能是正确的选择。在某些特定的情况,os_unfair_lock 也许更好。其他的通常不会被选择使用。总结Swift 没有对线程同步的语言支持,但是有 API 来补救。GCD 仍然是 Apple 皇冠上的一个宝石,它提供给 Swift 的 API 也非常棒。在少数它不合适的情况下,还有其他的选项提供选择。我们没有 @synchronized和原子属性(atomic properties),但是我们有更好的东西。赞赏还没有人赞赏,快来当第一个赞赏的人吧!4收藏分享举报文章被以下专栏收录{&debug&:false,&apiRoot&:&&,&paySDK&:&https:\u002F\u002Fpay.zhihu.com\u002Fapi\u002Fjs&,&wechatConfigAPI&:&\u002Fapi\u002Fwechat\u002Fjssdkconfig&,&name&:&production&,&instance&:&column&,&tokens&:{&X-XSRF-TOKEN&:null,&X-UDID&:null,&Authorization&:&oauth c3cef7c66aa9e6a1e3160e20&}}{&database&:{&Post&:{&&:{&isPending&:false,&contributes&:[{&sourceColumn&:{&lastUpdated&:,&description&:&&,&permission&:&COLUMN_PUBLIC&,&memberId&:259446,&contributePermission&:&COLUMN_PUBLIC&,&translatedCommentPermission&:&all&,&canManage&:true,&intro&:&&,&urlToken&:&kylinroc&,&id&:24416,&imagePath&:&v2-ca00aca6ed8849b9fdab46bf463e742b.png&,&slug&:&kylinroc&,&applyReason&:&0&,&name&:&假装在编程&,&title&:&假装在编程&,&url&:&https:\u002F\u002Fzhuanlan.zhihu.com\u002Fkylinroc&,&commentPermission&:&COLUMN_ALL_CAN_COMMENT&,&canPost&:true,&created&:,&state&:&COLUMN_NORMAL&,&followers&:65,&avatar&:{&id&:&v2-ca00aca6ed8849b9fdab46bf463e742b&,&template&:&https:\u002F\u002Fpic1.zhimg.com\u002F{id}_{size}.jpg&},&activateAuthorRequested&:false,&following&:false,&imageUrl&:&https:\u002F\u002Fpic1.zhimg.com\u002Fv2-ca00aca6ed8849b9fdab46bf463e742b_l.jpg&,&articlesCount&:20},&state&:&accepted&,&targetPost&:{&titleImage&:&&,&lastUpdated&:,&imagePath&:&&,&permission&:&ARTICLE_PUBLIC&,&topics&:[79,1724],&summary&:&本文原始地址:\u003Ca href=\&http:\u002F\u002Fkylinroc.github.io\u002Ffriday-qa--locks-thread-safety-and-swift-2017-edition.html\&\u003E【翻译】星期五问答 :锁、线程安全与 Swift:2017 版\u003C\u002Fa\u003E作者:\u003Ca href=\&https:\u002F\u002Fwww.mikeash.com\u002F\&\u003EMike Ash\u003C\u002Fa\u003E原文地址:\u003Ca href=\&https:\u002F\u002Fwww.mikeash.com\u002Fpyblog\u002Ffriday-qa--locks-thread-safety-and-swift-2017-edition.html\&\u003EFriday Q&A : Locks, Thread Safety, and Swift: 2017 Edition\u003C\u002Fa\u003E本文翻译经过原作者 Mike Ash 同意。在黑暗的 Swift 1 时代,我写了\u003Ca href=\&https:\u002F\u002Fwww.mikeash.com\u002Fpyblog\u002Ffriday-qa--locks-thread-safety-and-swift.html\&\u003E一篇关于 …\u003C\u002Fa\u003E&,&copyPermission&:&ARTICLE_COPYABLE&,&translatedCommentPermission&:&all&,&likes&:0,&origAuthorId&:0,&publishedTime&:&T22:19:51+08:00&,&sourceUrl&:&&,&urlToken&:,&id&:4411074,&withContent&:false,&slug&:,&bigTitleImage&:false,&title&:&【翻译】星期五问答 :锁、线程安全与 Swift:2017 版&,&url&:&\u002Fp\u002F&,&commentPermission&:&ARTICLE_ALL_CAN_COMMENT&,&snapshotUrl&:&&,&created&:,&comments&:0,&columnId&:0,&content&:&&,&parentId&:0,&state&:&ARTICLE_PUBLISHED&,&imageUrl&:&&,&author&:{&bio&:&一句话不说也不好&,&isFollowing&:false,&hash&:&d591eb2cbe43ae46b610ed&,&uid&:84,&isOrg&:false,&slug&:&charizard&,&isFollowed&:false,&description&:&http:\u002F\u002Fkylinroc.github.io&,&name&:&李孛&,&profileUrl&:&https:\u002F\u002Fwww.zhihu.com\u002Fpeople\u002Fcharizard&,&avatar&:{&id&:&v2-efef715ed6bfd&,&template&:&https:\u002F\u002Fpic1.zhimg.com\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},&memberId&:259446,&excerptTitle&:&&,&voteType&:&ARTICLE_VOTE_CLEAR&},&id&:893229}],&title&:&【翻译】星期五问答 :锁、线程安全与 Swift:2017 版&,&author&:&charizard&,&content&:&\u003Cblockquote\u003E本文原始地址:\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Fkylinroc.github.io\u002Ffriday-qa--locks-thread-safety-and-swift-2017-edition.html\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E【翻译】星期五问答 :锁、线程安全与 Swift:2017 版\u003C\u002Fa\u003E\u003C\u002Fblockquote\u003E\u003Cul\u003E\u003Cli\u003E作者:\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Fwww.mikeash.com\u002F\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003EMike Ash\u003C\u002Fa\u003E\u003C\u002Fli\u003E\u003Cli\u003E原文地址:\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Fwww.mikeash.com\u002Fpyblog\u002Ffriday-qa--locks-thread-safety-and-swift-2017-edition.html\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003EFriday Q&A : Locks, Thread Safety, and Swift: 2017 Edition\u003C\u002Fa\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cblockquote\u003E本文翻译经过原作者 Mike Ash 同意。\u003C\u002Fblockquote\u003E\u003Cp\u003E在黑暗的 Swift 1 时代,我写了\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Fwww.mikeash.com\u002Fpyblog\u002Ffriday-qa--locks-thread-safety-and-swift.html\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003E一篇关于 Swift 中锁和线程安全的文章\u003C\u002Fa\u003E。时间的流逝使这篇文章过时了,读者 \u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Ftwitter.com\u002Fsethwillits\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003ESeth Willits\u003C\u002Fa\u003E 建议我根据现在的情况更新它,所以有了这篇文章!\u003C\u002Fp\u003E\u003Cp\u003E这篇文章会重新提到老文章里的一些内容,不过会将它们更新到最新的情况,还会有一些关于它们改变的讨论。阅读这篇文章不要求读过老文章。\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E锁的快速回顾\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E锁(lock)或者互斥锁(mutex)是一种保证一段代码在任何时候只有一个线程在执行的结构。它们通常被用来保证多个线程访问同一个可变数据结构时的数据一致性。有这么几个类型的锁:\u003C\u002Fp\u003E\u003Col\u003E\u003Cli\u003E阻塞锁(blocking locks)在等待其它线程释放锁时会睡眠线程。阻塞锁是一般情况下使用的锁。\u003C\u002Fli\u003E\u003Cli\u003E自旋锁(spinlocks)使用一个忙等循环(busy loop)不断的检查锁是否被释放。自旋锁在等待较少的情况下很高效,但是在等待较多的情况下非常浪费 CPU 时间。\u003C\u002Fli\u003E\u003Cli\u003E读写锁(reader\u002Fwriter locks)允许多个「读」线程同时进入一段代码,但是阻止其他线程(包括读线程)进入当一个「写」线程获取到锁时。在很多数据结构被多个线程同时读取是安全的,但是有多个线程读或写时写数据是不安全的情况下,读写锁是很有用的。\u003C\u002Fli\u003E\u003Cli\u003E递归锁(recursive locks)允许同一个线程多次获取同一个锁。非递归锁在被同一个线程重复获取时可能会导致死锁、崩溃或者其他不正常行为。\u003C\u002Fli\u003E\u003C\u002Fol\u003E\u003Ch2\u003E\u003Cb\u003EAPIs\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003EApple 提供了一大堆锁的 API。下面有一个很长但不包含全部锁的列表:\u003C\u002Fp\u003E\u003Col\u003E\u003Cli\u003E\u003Ccode\u003Epthread_mutex_t\u003C\u002Fcode\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Ccode\u003Epthread_rwlock_t\u003C\u002Fcode\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Ccode\u003EDispatchQueue\u003C\u002Fcode\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Ccode\u003EOperationQueue\u003C\u002Fcode\u003E 当配置为 serial 时\u003C\u002Fli\u003E\u003Cli\u003E\u003Ccode\u003ENSLock\u003C\u002Fcode\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Ccode\u003Eos_unfair_lock\u003C\u002Fcode\u003E\u003C\u002Fli\u003E\u003C\u002Fol\u003E\u003Cp\u003E除了这些以外,Objective-C 提供了 \u003Ccode\u003E@synchronized\u003C\u002Fcode\u003E 语法支持,现在这个时间点它的底层是由 \u003Ccode\u003Epthread_mutex_t\u003C\u002Fcode\u003E 实现的。不像其他的 API,\u003Ccode\u003E@synchronized\u003C\u002Fcode\u003E 不使用明确的锁对象,而是把任意的 Objective-C 对象当作锁。一段 \u003Ccode\u003E@synchronized(someObject)\u003C\u002Fcode\u003E 代码会阻塞其他使用相同对象指针的 \u003Ccode\u003E@synchronized\u003C\u002Fcode\u003E 代码。这些不同的 API 有着不同的行为和功能:\u003C\u002Fp\u003E\u003Col\u003E\u003Cli\u003E\u003Ccode\u003Epthread_mutex_t\u003C\u002Fcode\u003E 是一种阻塞锁,并且可以被配置为递归锁。\u003C\u002Fli\u003E\u003Cli\u003E\u003Ccode\u003Epthread_rwlock_t\u003C\u002Fcode\u003E 是一种读写锁。\u003C\u002Fli\u003E\u003Cli\u003E\u003Ccode\u003EDispatchQueue\u003C\u002Fcode\u003E 可以被当作阻塞锁使用。也可以通过将其配置为并发队列并配合内存屏障作为读写锁使用。还支持异步执行加锁代码。\u003C\u002Fli\u003E\u003Cli\u003E\u003Ccode\u003EOperationQueue\u003C\u002Fcode\u003E 可以被用作阻塞锁。和 \u003Ccode\u003EDispatchQueue\u003C\u002Fcode\u003E 一样,支持异步执行加锁代码。\u003C\u002Fli\u003E\u003Cli\u003E\u003Ccode\u003ENSLock\u003C\u002Fcode\u003E 是一种 Objective-C 类包装的阻塞锁。它的小伙伴 \u003Ccode\u003ENSRecursiveLock\u003C\u002Fcode\u003E 是一种递归锁。\u003C\u002Fli\u003E\u003Cli\u003E\u003Ccode\u003Eos_unfair_lock\u003C\u002Fcode\u003E 是一种简单(less sophisticated)、低级(lower-level)的阻塞锁。\u003C\u002Fli\u003E\u003C\u002Fol\u003E\u003Cp\u003E最后,\u003Ccode\u003E@synchronized\u003C\u002Fcode\u003E 是一种阻塞、递归锁。\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E没有自旋锁\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E我在锁的不同类型里提到了自旋锁,但是列出来的 API 里并没有自旋锁。这是这篇文章和老文章有很大不同的地方,也是我写这篇文章的主要原因。\u003C\u002Fp\u003E\u003Cp\u003E自旋锁非常简单,在合适的情况下非常高效。不幸的是,在复杂的现代世界里,它太简单了。\u003C\u002Fp\u003E\u003Cp\u003E问题出于线程优先级(thread priorities)。当可运行的线程数超过 CPU 核数时,高优先级的线程会被优先选择。这是一个有用主意,因为 CPU 核心永远是有限的资源,你不会希望当你的用户使用 UI 时被一些时间不敏感的后台网络操作偷取了 CPU 时间。\u003C\u002Fp\u003E\u003Cp\u003E当一个高优先级线程被阻塞,等待低优先级线程完成工作,但是高优先级线程阻止低优先级线程实际执行工作,这会导致长时间的无反应或者永久的死锁。\u003C\u002Fp\u003E\u003Cp\u003E发生死锁的情况像下面描述的这样,使用 H 代表高优先级线程,L 代表低优先级线程:\u003C\u002Fp\u003E\u003Col\u003E\u003Cli\u003EL 获得自旋锁。\u003C\u002Fli\u003E\u003Cli\u003EL 开始执行工作。\u003C\u002Fli\u003E\u003Cli\u003EH 可以运行了,并且抢占了 L 的核心。\u003C\u002Fli\u003E\u003Cli\u003EH 尝试获得自旋锁,但是因为 L 还持有着而失败。\u003C\u002Fli\u003E\u003Cli\u003EH 开始愤怒的旋转自旋锁,不停的尝试获取它,并且霸占了 CPU。\u003C\u002Fli\u003E\u003Cli\u003EH 不能继续执行知道 L 完成它的工作。L 不能完成它的工作除非 H 停止愤怒的旋转自旋锁。\u003C\u002Fli\u003E\u003Cli\u003E悲伤的故事。\u003C\u002Fli\u003E\u003C\u002Fol\u003E\u003Cp\u003E有很多方法可以解决这个问题。比如,H 可以在第 4 步将它的优先级捐献给 L,让 L 快速的完成工作。让自旋锁解决这个问题是可能的,但是 Apple 的老旧自旋锁 API \u003Ccode\u003EOSSpinLock\u003C\u002Fcode\u003E 不能。\u003C\u002Fp\u003E\u003Cp\u003E这在很长一段时间里并不是问题,因为线程优先级在 Apple 的平台上并没有怎么使用,并且优先级系统使用动态优先级调整来防止上面提到的死锁情况持续太久。近来,\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Fdeveloper.apple.com\u002Flibrary\u002Fcontent\u002Fdocumentation\u002FPerformance\u002FConceptual\u002FEnergyGuide-iOS\u002FPrioritizeWorkWithQoS.html\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003Equality of service classes\u003C\u002Fa\u003E 使不同优先级的使用更常见了,也使得上面这种死锁的情况更常见了。\u003C\u002Fp\u003E\u003Cp\u003E\u003Ccode\u003EOSSpinLock\u003C\u002Fcode\u003E 良好的工作了很长时间,但在 iOS 8 和 macOS 10.10 发布后不再是一个好的工具了。现在已正式的被弃用。替代它的是 \u003Ccode\u003Eos_unfair_lock\u003C\u002Fcode\u003E,符合原本低级、简单和开销小的要求,但足够精巧来避免线程优先级带来的问题。\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E值类型\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E注意 \u003Ccode\u003Epthread_mutex_t\u003C\u002Fcode\u003E、\u003Ccode\u003Epthread_rwlock_t\u003C\u002Fcode\u003E 和 \u003Ccode\u003Eos_unfair_lock\u003C\u002Fcode\u003E 都是值类型,不是引用类型。这代表着如果你对它们使用 \u003Ccode\u003E=\u003C\u002Fcode\u003E,你会得到一份拷贝。这很重要,因为这些类型不能被拷贝!如果你拷贝一份 \u003Ccode\u003Epthread\u003C\u002Fcode\u003E 类型,拷贝是不可用的,当你使用它时可能会导致崩溃。和这些类型一起使用的 \u003Ccode\u003Epthread\u003C\u002Fcode\u003E 函数们认为这些类型的值存放在它们初始化时的内存地址上,之后将它们移动到别的位置不是一个好主意。\u003Ccode\u003Eos_unfair_lock\u003C\u002Fcode\u003E 不会崩溃,但是你将会得到一个你永远不会想要的结果,一个完全不一样的锁。\u003C\u002Fp\u003E\u003Cp\u003E如果你使用这些类型,你必须保证永远不要拷贝它们,不管是明确的使用了 \u003Ccode\u003E=\u003C\u002Fcode\u003E 操作符,或者是无意的,比如将它们放入 \u003Ccode\u003Estruct\u003C\u002Fcode\u003E 或者被闭包捕获。\u003C\u002Fp\u003E\u003Cp\u003E另外,因为锁本质上是一个可变对象,所以你需要使用 \u003Ccode\u003Evar\u003C\u002Fcode\u003E 而不是 \u003Ccode\u003Elet\u003C\u002Fcode\u003E 来声明它们。\u003C\u002Fp\u003E\u003Cp\u003E其他的则是引用类型,你可以随你喜欢的四处传递它们,并且可以用 \u003Ccode\u003Elet\u003C\u002Fcode\u003E 来声明。\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E初始化\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E你需要小心对待 \u003Ccode\u003Epthread\u003C\u002Fcode\u003E 锁,因为你可以通过空()构造函数来创建一个值,但是这个值还不是一个有效的锁。这些锁必须分别通过使用 \u003Ccode\u003Epthread_mutex_init\u003C\u002Fcode\u003E 或者 \u003Ccode\u003Epthread_rwlock_init\u003C\u002Fcode\u003E 来初始化:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-swift\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&kd\&\u003Evar\u003C\u002Fspan\u003E \u003Cspan class=\&nv\&\u003Emutex\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Epthread_mutex_t\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E()\u003C\u002Fspan\u003E\n\u003Cspan class=\&n\&\u003Epthread_mutex_init\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(&\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Emutex\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&kc\&\u003Enil\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E \n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E给这些类型写一个扩展(extension)来包装初始化过程是很诱人的。然而这无法保证构造函数直接工作到一个变量而不是一份拷贝上。因为这些类型无法安全的被拷贝,这样的扩展是无法安全的写出来的,除非你返回一个指针或者一个包装类(wrapper class)。\u003C\u002Fp\u003E\u003Cp\u003E如果你使用这些 API,销毁锁时别忘了调用相应的 \u003Ccode\u003Edestory\u003C\u002Fcode\u003E 函数。\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E使用\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E\u003Ccode\u003EDispatchQueue\u003C\u002Fcode\u003E 有着基于回调(callback-based)的 API,这使得对它的使用有着天然的安全性。取决于你想同步或者异步的执行被保护的代码,调用 \u003Ccode\u003Esync\u003C\u002Fcode\u003E 或者 \u003Ccode\u003Easync\u003C\u002Fcode\u003E 并传入代码来运行:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-swift\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Equeue\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Esync\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Eexecute\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E...\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E})\u003C\u002Fspan\u003E\n\u003Cspan class=\&n\&\u003Equeue\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Easync\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Eexecute\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E...\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E})\u003C\u002Fspan\u003E \n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E对于 \u003Ccode\u003Esync\u003C\u002Fcode\u003E 来说,它提供了一个很棒的特性,将被保护的代码的返回值捕获为 \u003Ccode\u003Esync\u003C\u002Fcode\u003E 方法的返回值:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-swift\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&kd\&\u003Elet\u003C\u002Fspan\u003E \u003Cspan class=\&nv\&\u003Evalue\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Equeue\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Esync\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Eexecute\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E \u003Cspan class=\&k\&\u003Ereturn\u003C\u002Fspan\u003E \u003Cspan class=\&kc\&\u003Eself\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EprotectedProperty\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E})\u003C\u002Fspan\u003E \n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E你甚至可以在被保护的代码中 \u003Ccode\u003Ethrow\u003C\u002Fcode\u003E 错误,它会将其传递出来。\u003C\u002Fp\u003E\u003Cp\u003E\u003Ccode\u003EOperationQueue\u003C\u002Fcode\u003E 很相似,但是没有内建的方法来传出返回值或者错误。你需要自己实现,或者使用 \u003Ccode\u003EDispatchQueue\u003C\u002Fcode\u003E 来替代。\u003C\u002Fp\u003E\u003Cp\u003E其他的 API 需要单独的加锁和解锁调用,当你忘了调用其中一个时会非常刺激。这些调用长这样:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-swift\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Epthread_mutex_lock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(&\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Emutex\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E...\u003C\u002Fspan\u003E\n\u003Cspan class=\&n\&\u003Epthread_mutex_unlock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(&\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Emutex\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&n\&\u003Enslock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Elock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E()\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E...\u003C\u002Fspan\u003E\n\u003Cspan class=\&n\&\u003Enslock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Eunlock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E()\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&n\&\u003Eos_unfair_lock_lock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(&\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Elock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E...\u003C\u002Fspan\u003E\n\u003Cspan class=\&n\&\u003Eos_unfair_lock_unlock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(&\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Elock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E \n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E因为这些 API 都一模一样,接下来的例子我只会使用 \u003Ccode\u003Enslock\u003C\u002Fcode\u003E。其他除了函数名以外都是一样的。\u003C\u002Fp\u003E\u003Cp\u003E当被保护的代码很简单的时候,这可以工作的很好。但是如果情况更复杂呢?举个例子:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-swift\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Enslock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Elock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E()\u003C\u002Fspan\u003E\n\u003Cspan class=\&k\&\u003Eif\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EearlyExitCondition\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&k\&\u003Ereturn\u003C\u002Fspan\u003E \u003Cspan class=\&kc\&\u003Enil\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n\u003Cspan class=\&kd\&\u003Elet\u003C\u002Fspan\u003E \u003Cspan class=\&nv\&\u003Evalue\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ecompute\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E()\u003C\u002Fspan\u003E\n\u003Cspan class=\&n\&\u003Enslock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Eunlock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E()\u003C\u002Fspan\u003E\n\u003Cspan class=\&k\&\u003Ereturn\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Evalue\u003C\u002Fspan\u003E \n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E哎哟,有时候你没有解锁!这是一种写出难以找到的 bug 的方法。也许你总是正确的对待你的 \u003Ccode\u003Ereturn\u003C\u002Fcode\u003E 语句,绝不会写出这种代码。但如果你要抛出错误呢?\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-swift\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Enslock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Elock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E()\u003C\u002Fspan\u003E\n\u003Cspan class=\&n\&\u003Eguard\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Esomething\u003C\u002Fspan\u003E \u003Cspan class=\&k\&\u003Eelse\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ethrow\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Eerror\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n\u003Cspan class=\&kd\&\u003Elet\u003C\u002Fspan\u003E \u003Cspan class=\&nv\&\u003Evalue\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ecompute\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E()\u003C\u002Fspan\u003E\n\u003Cspan class=\&n\&\u003Enslock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Eunlock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E()\u003C\u002Fspan\u003E\n\u003Cspan class=\&k\&\u003Ereturn\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Evalue\u003C\u002Fspan\u003E \n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E同样的问题!也许你非常的自律并且从不会写这种代码。虽然你的代码是安全的,但是代码有那么一点丑陋:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-swift\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Enslock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Elock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E()\u003C\u002Fspan\u003E\n\u003Cspan class=\&kd\&\u003Elet\u003C\u002Fspan\u003E \u003Cspan class=\&nv\&\u003Evalue\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ecompute\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E()\u003C\u002Fspan\u003E\n\u003Cspan class=\&n\&\u003Enslock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Eunlock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E()\u003C\u002Fspan\u003E\n\u003Cspan class=\&k\&\u003Ereturn\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Evalue\u003C\u002Fspan\u003E \n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E明显的修复方式是使用 Swift 的 \u003Ccode\u003Edefer\u003C\u002Fcode\u003E 语法。在你加锁的时候,defer 接锁。不管你怎么样退出代码,锁一定会被释放:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-swift\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Enslock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Elock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E()\u003C\u002Fspan\u003E\n\u003Cspan class=\&n\&\u003Edefer\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Enslock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Eunlock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E()\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n\u003Cspan class=\&k\&\u003Ereturn\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ecompute\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E()\u003C\u002Fspan\u003E \n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E这对提早退出、抛错或者正常退出都有效。\u003C\u002Fp\u003E\u003Cp\u003E写这两行代码还是会让人不爽,所以让我们将这些事情包装成一个像 \u003Ccode\u003EDispatchQueue\u003C\u002Fcode\u003E 那样基于回调的函数:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-swift\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&kd\&\u003Efunc\u003C\u002Fspan\u003E \u003Cspan class=\&nf\&\u003EwithLocked\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E&\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003ET\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E&(\u003C\u002Fspan\u003E\u003Cspan class=\&kc\&\u003E_\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Elock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&bp\&\u003ENSLock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \n
\u003Cspan class=\&kc\&\u003E_\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ef\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E()\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ethrows\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E-&\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003ET\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Erethrows\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E-&\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003ET\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&n\&\u003Elock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Elock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E()\u003C\u002Fspan\u003E\n
\u003Cspan class=\&n\&\u003Edefer\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Elock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Eunlock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E()\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n
\u003Cspan class=\&k\&\u003Ereturn\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Etry\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ef\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E()\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&kd\&\u003Elet\u003C\u002Fspan\u003E \u003Cspan class=\&nv\&\u003Evalue\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EwithLocked\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Elock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E \u003Cspan class=\&k\&\u003Ereturn\u003C\u002Fspan\u003E \u003Cspan class=\&kc\&\u003Eself\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E.\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EprotectedProperty\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E})\u003C\u002Fspan\u003E \n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E当为值类型实现这个函数的时候,你需要接收一个锁的\u003Cb\u003E指针\u003C\u002Fb\u003E而不是锁本身作为参数。记住,你不会想要拷贝这些玩意!\u003Ccode\u003Epthread\u003C\u002Fcode\u003E 版本长这样:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-swift\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&kd\&\u003Efunc\u003C\u002Fspan\u003E \u003Cspan class=\&nf\&\u003EwithLocked\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E&\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003ET\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E&(\u003C\u002Fspan\u003E\u003Cspan class=\&kc\&\u003E_\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EmutexPtr\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&nb\&\u003EUnsafeMutablePointer\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E&\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Epthread_mutex_t\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E&,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&kc\&\u003E_\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ef\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E()\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ethrows\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E-&\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003ET\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Erethrows\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E-&\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003ET\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&n\&\u003Epthread_mutex_lock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EmutexPtr\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\n
\u003Cspan class=\&n\&\u003Edefer\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Epthread_mutex_unlock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EmutexPtr\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n
\u003Cspan class=\&k\&\u003Ereturn\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Etry\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ef\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E()\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E \n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Ch2\u003E\u003Cb\u003E选择你的锁 API\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E\u003Ccode\u003EDispatchQueue\u003C\u002Fcode\u003E 显然是最受喜爱的。它有着 Swifty 的 API,用起来也很爽。Apple 对 Dispatch 这个库投入了很多注意,这表明你可以指望它性能良好、运行可靠并且获得一堆炫酷的新功能。\u003C\u002Fp\u003E\u003Cp\u003E\u003Ccode\u003EDispatchQueue\u003C\u002Fcode\u003E 允许很多有用的进阶用法,比如在被你当作锁的队列上触发安排好的定时器(timers)或者事件源(event sources),保证它们的 handler 和其他使用这个队列的东西同步。设定目标队列(target queues)的能力允许表示复杂的锁层级。自定义并发队列可以很容易的被用作读写锁。你只要改变一个字母就能从同步执行变成异步的在一个后台线程上执行被保护的代码。 并且它的 API 很容易使用,很难被误用。不管在哪方面都很优秀。这都是 GCD 快速变为我最喜欢的 API 之一的原因。\u003C\u002Fp\u003E\u003Cp\u003E像所有的事物一样,它不是完美的。一个 Dispatch 队列是由一个内存中的对象表示的,所以开销有一点点大。它还缺少一些有用的功能,比如条件变量(condition variables)和递归性。总会有一些时候,使用独立的加锁和解锁调用比被强制使用基于回调的 API 更好使。\u003Ccode\u003EDispatchQueue\u003C\u002Fcode\u003E 通常是正确的选择,同样也是你不知道该选择什么时最棒的默认选项,但偶尔会有一些原因需要使用其他的 API。\u003C\u002Fp\u003E\u003Cp\u003E\u003Ccode\u003Eos_unfair_lock\u003C\u002Fcode\u003E 是一个当每个锁的开销不能太大(因为某些原因你可能有一大堆锁)并且不需要一些华丽功能时的正确选择。它由一个可以放在任何地方的 32 位整数实现,所以开销非常小。\u003C\u002Fp\u003E\u003Cp\u003E像它的名字提示的一样,\u003Ccode\u003Eos_unfair_lock\u003C\u002Fcode\u003E 的一个特性就是没有公平性。锁的公平性表示至少会有企图,保证所有等待锁释放的线程都有机会获得锁。没有公平性的话,一个线程可以在有其他线程等待时通过快速释放和重新获取锁来独占一个锁。\u003C\u002Fp\u003E\u003Cp\u003E这是不是一个问题取决于你在做什么。有一些时候公平性是必要的,有一些时候公平性是没有什么用的。缺少公平性使得 \u003Ccode\u003Eos_unfair_lock\u003C\u002Fcode\u003E 有着更好的性能,使它在公平性不需要的场景更有优势。\u003C\u002Fp\u003E\u003Cp\u003E\u003Ccode\u003Epthread_mutex\u003C\u002Fcode\u003E 夹在上面两者之间。它比 \u003Ccode\u003Eos_unfair_lock\u003C\u002Fcode\u003E 明显大很多,有 64 字节,但你还是可以控制它存储在哪里。它实现了公平性,尽管这是 Apple 的实现中的细节,而不是 API 标准中写出的。它也提供了各种进阶功能,比如使锁递归的能力和复杂的线程优先级相关的玩意。\u003C\u002Fp\u003E\u003Cp\u003E\u003Ccode\u003Epthread_rwlock\u003C\u002Fcode\u003E 提供了一种读写锁。它占用巨大的 200 字节但并没有提供什么有趣的功能,所以看起来并没有多少理由使用它而不使用一个并发的 \u003Ccode\u003EDispatchQueue\u003C\u002Fcode\u003E。\u003C\u002Fp\u003E\u003Cp\u003E\u003Ccode\u003ENSLock\u003C\u002Fcode\u003E 是对 \u003Ccode\u003Epthread_mutex\u003C\u002Fcode\u003E 的包装。很难想象出一个它的用例,但当你需要明确的加锁和解锁调用但是不想要 \u003Ccode\u003Epthread_mutex\u003C\u002Fcode\u003E 那些麻烦的手动初始化和销毁时它会很有用。\u003C\u002Fp\u003E\u003Cp\u003E\u003Ccode\u003EOperationQueue\u003C\u002Fcode\u003E 提供了和 \u003Ccode\u003EDispatchQueue\u003C\u002Fcode\u003E 相似的基于回调的 API,还有一下进阶功能比如 operation 间的依赖管理,但是缺少很多其他 \u003Ccode\u003EDispatchQueue\u003C\u002Fcode\u003E 提供的功能。尽管它在做别的事情时很有用,但 \u003Ccode\u003EOperationQueue\u003C\u002Fcode\u003E 没有什么原因被用作加锁的 API。\u003C\u002Fp\u003E\u003Cp\u003E总之:\u003Ccode\u003EDispatchQueue\u003C\u002Fcode\u003E 很可能是正确的选择。在某些特定的情况,\u003Ccode\u003Eos_unfair_lock\u003C\u002Fcode\u003E 也许更好。其他的通常不会被选择使用。\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E总结\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003ESwift 没有对线程同步的语言支持,但是有 API 来补救。GCD 仍然是 Apple 皇冠上的一个宝石,它提供给 Swift 的 API 也非常棒。在少数它不合适的情况下,还有其他的选项提供选择。我们没有 \u003Ccode\u003E@synchronized\u003C\u002Fcode\u003E和原子属性(atomic properties),但是我们有更好的东西。\u003C\u002Fp\u003E&,&updated&:new Date(&T14:19:51.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:0,&collapsedCount&:0,&likeCount&:4,&state&:&published&,&isLiked&:false,&slug&:&&,&lastestTipjarors&:[],&isTitleImageFullScreen&:false,&rating&:&none&,&titleImage&:&&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&reviewers&:[],&topics&:[{&url&:&https:\u002F\u002Fwww.zhihu.com\u002Ftopic\u002F&,&id&:&&,&name&:&Swift 语言&},{&url&:&https:\u002F\u002Fwww.zhihu.com\u002Ftopic\u002F&,&id&:&&,&name&:&线程安全&},{&url&:&https:\u002F\u002Fwww.zhihu.com\u002Ftopic\u002F&,&id&:&&,&name&:&iOS 开发&}],&adminClosedComment&:false,&titleImageSize&:{&width&:0,&height&:0},&href&:&\u002Fapi\u002Fposts\u002F&,&excerptTitle&:&&,&tipjarState&:&activated&,&tipjarTagLine&:&真诚赞赏,手留余香&,&sourceUrl&:&&,&pageCommentsCount&:0,&tipjarorCount&:0,&annotationAction&:[],&hasPublishingDraft&:false,&snapshotUrl&:&&,&publishedTime&:&T22:19:51+08:00&,&url&:&\u002Fp\u002F&,&lastestLikers&:[{&bio&:&咬过的苹果,谁要?&,&isFollowing&:false,&hash&:&4d8ce9dcf965cca9be4fa5d6d53b8963&,&uid&:36,&isOrg&:false,&slug&:&tu-tu-edmondmu&,&isFollowed&:false,&description&:&男,未婚&,&name&:&土土Edmond木&,&profileUrl&:&https:\u002F\u002Fwww.zhihu.com\u002Fpeople\u002Ftu-tu-edmondmu&,&avatar&:{&id&:&v2-72daa3b0f&,&template&:&https:\u002F\u002Fpic1.zhimg.com\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},{&bio&:&&,&isFollowing&:false,&hash&:&6d2207c9afb941e6cafc6a913f1af08a&,&uid&:64,&isOrg&:false,&slug&:&pyanfield&,&isFollowed&:false,&description&:&&,&name&:&pyanfield&,&profileUrl&:&https:\u002F\u002Fwww.zhihu.com\u002Fpeople\u002Fpyanfield&,&avatar&:{&id&:&v2-ebbee5337492bbd&,&template&:&https:\u002F\u002Fpic1.zhimg.com\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},{&bio&:&abcd&,&isFollowing&:false,&hash&:&cdf5abc0eaf538b50940a&,&uid&:52,&isOrg&:false,&slug&:&cs-shen&,&isFollowed&:false,&description&:&&,&name&:&cs shen&,&profileUrl&:&https:\u002F\u002Fwww.zhihu.com\u002Fpeople\u002Fcs-shen&,&avatar&:{&id&:&ed3c8e9767ded19e028fea&,&template&:&https:\u002F\u002Fpic4.zhimg.com\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},{&bio&:&http:\u002F\u002Fwww.dreampiggy.com https:\u002F\u002Fgithub.com\u002Fdreampiggy&,&isFollowing&:false,&hash&:&1038295afbed8ffdaac57b1c649a7521&,&uid&:72,&isOrg&:false,&slug&:&dreampiggy&,&isFollowed&:false,&description&:&已经毕业的大四狗。\n程序员,Swift的ABI今天稳定了吗?\n独立游戏,文字游戏,主机游戏爱好者。&,&name&:&DreamPiggy&,&profileUrl&:&https:\u002F\u002Fwww.zhihu.com\u002Fpeople\u002Fdreampiggy&,&avatar&:{&id&:&c0636a53d&,&template&:&https:\u002F\u002Fpic1.zhimg.com\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false}],&summary&:&本文原始地址:\u003Ca href=\&http:\u002F\u002Fkylinroc.github.io\u002Ffriday-qa--locks-thread-safety-and-swift-2017-edition.html\&\u003E【翻译】星期五问答 :锁、线程安全与 Swift:2017 版\u003C\u002Fa\u003E作者:\u003Ca href=\&https:\u002F\u002Fwww.mikeash.com\u002F\&\u003EMike Ash\u003C\u002Fa\u003E原文地址:\u003Ca href=\&https:\u002F\u002Fwww.mikeash.com\u002Fpyblog\u002Ffriday-qa--locks-thread-safety-and-swift-2017-edition.html\&\u003EFriday Q&A : Locks, Thread Safety, and Swift: 2017 Edition\u003C\u002Fa\u003E本文翻译经过原作者 Mike Ash 同意。在黑暗的 Swift 1 时代,我写了\u003Ca href=\&https:\u002F\u002Fwww.mikeash.com\u002Fpyblog\u002Ffriday-qa--locks-thread-safety-and-swift.html\&\u003E一篇关于 …\u003C\u002Fa\u003E&,&reviewingCommentsCount&:0,&meta&:{&previous&:null,&next&:null},&annotationDetail&:null,&commentsCount&:0,&likesCount&:4,&FULLINFO&:true}},&User&:{&charizard&:{&isFollowed&:false,&name&:&李孛&,&headline&:&http:\u002F\u002Fkylinroc.github.io&,&avatarUrl&:&https:\u002F\u002Fpic1.zhimg.com\u002Fv2-efef715ed6bfd_s.jpg&,&isFollowing&:false,&type&:&people&,&slug&:&charizard&,&bio&:&一句话不说也不好&,&hash&:&d591eb2cbe43ae46b610ed&,&uid&:84,&isOrg&:false,&description&:&http:\u002F\u002Fkylinroc.github.io&,&badge&:{&identity&:null,&bestAnswerer&:null},&profileUrl&:&https:\u002F\u002Fwww.zhihu.com\u002Fpeople\u002Fcharizard&,&avatar&:{&id&:&v2-efef715ed6bfd&,&template&:&https:\u002F\u002Fpic1.zhimg.com\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false}},&Comment&:{},&favlists&:{}},&me&:{},&global&:{&experimentFeatures&:{&ge3&:&ge3_9&,&ge2&:&ge2_1&,&growthSearch&:&s2&,&sEI&:&c&,&nwebQAGrowth&:&experiment&,&qawebRelatedReadingsContentControl&:&close&,&liveStore&:&ls_a2_b2_c1_f2&,&qawebThumbnailAbtest&:&new&,&nwebSearch&:&nweb_search_heifetz&,&rt&:&y&,&showVideoUploadAttention&:&true&,&isOffice&:&false&,&enableTtsPlay&:&post&,&newQuestionDiversion&:&https:\u002F\u002Fzhuanlan.zhihu.com\u002Fp\u002F&,&newLiveFeedMediacard&:&new&,&newMobileAppHeader&:&true&,&androidPassThroughPush&:&all&,&hybridZhmoreVideo&:&yes&,&nwebGrowthPeople&:&default&,&nwebSearchSuggest&:&default&,&qrcodeLogin&:&qrcode&,&enableVoteDownReasonMenu&:&enable&,&isf8&:&1&,&isShowUnicomFreeEntry&:&unicom_free_entry_off&,&newMobileColumnAppheader&:&new_header&,&androidDbRecommendAction&:&open&,&zcmLighting&:&zcm&,&androidDbFeedHashTagStyle&:&button&,&appStoreRateDialog&:&close&,&default&:&None&,&isNewNotiPanel&:&no&,&wechatShareModal&:&wechat_share_modal_show&,&growthBanner&:&default&,&androidProfilePanel&:&panel_b&}},&columns&:{&next&:{}},&columnPosts&:{},&columnSettings&:{&colomnAuthor&:[],&uploadAvatarDetails&:&&,&contributeRequests&:[],&contributeRequestsTotalCount&:0,&inviteAuthor&:&&},&postComments&:{},&postReviewComments&:{&comments&:[],&newComments&:[],&hasMore&:true},&favlistsByUser&:{},&favlistRelations&:{},&promotions&:{},&switches&:{&couldSetPoster&:false},&draft&:{&titleImage&:&&,&titleImageSize&:{},&isTitleImageFullScreen&:false,&canTitleImageFullScreen&:false,&title&:&&,&titleImageUploading&:false,&error&:&&,&content&:&&,&draftLoading&:false,&globalLoading&:false,&pendingVideo&:{&resource&:null,&error&:null}},&drafts&:{&draftsList&:[],&next&:{}},&config&:{&userNotBindPhoneTipString&:{}},&recommendPosts&:{&articleRecommendations&:[],&columnRecommendations&:[]},&env&:{&edition&:{&baidu&:false,&yidianzixun&:false,&qqnews&:false},&isAppView&:false,&appViewConfig&:{&content_padding_top&:128,&content_padding_bottom&:56,&content_padding_left&:16,&content_padding_right&:16,&title_font_size&:22,&body_font_size&:16,&is_dark_theme&:false,&can_auto_load_image&:true,&app_info&:&OS=iOS&},&isApp&:false,&userAgent&:{&ua&:&Mozilla\u002F5.0 (compatible, MSIE 11, Windows NT 6.3; Trident\u002F7.0; rv:11.0) like Gecko&,&browser&:{&name&:&IE&,&version&:&11&,&major&:&11&},&engine&:{&version&:&7.0&,&name&:&Trident&},&os&:{&name&:&Windows&,&version&:&8.1&},&device&:{},&cpu&:{}}},&message&:{&newCount&:0},&pushNotification&:{&newCount&:0}}}

我要回帖

更多关于 linux 递归锁 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信