Swift GCD

基础知识

iOS 多线程实现方式

  • Grand Central Dispatch (GCD): 自动管理线程生命周期
  • NSOperation: 自动管理线程生命周期,基于 GCD 实现
  • NSThread: 手动管理线程生命周期

并发和并行

  • 并发: 并发不是真正的多线程,而是通过CPU(单核)在多个线程间快速切换调度,实现接近同时执行的效果
  • 并行: 并行是真正的多线程,多个线程能同时执行

同步和异步

  • 同步:当前线程里按顺序执行多项任务,且任务结束的顺序和任务开始执行的顺序是一致的。执行同步任务不会开启新线程,所有的任务都在当前线程执行。把一个同步任务分配出去后,当前线程会一直等到这个任务执行完才能接着运行后面的代码(因为同步任务本身就是分配给当前线程干的)
  • 异步:异步任务也是按顺序分配给线程的,但是可能会放在多个线程里执行的,所以每个任务结束的顺序有可能是随机的。执行异步任务可能会开启新线程,要具体情况具体分析。把一个异步任务分配出去后,当前线程不用等这个任务执行完就可以执行后面的代码,因为这个异步任务有可能交给别的线程去做了

串行队列和并行队列

  • 串行队列:按顺序分配任务,但是只有等上一个任务结束运行了,下一个任务才能分配出去
  • 并行队列:按顺序分配任务,下一个任务不必等上一个任务运行结束就能分配出去

也就是说,串行队列能够保证在分配某个任务的时候,在它之前分配出去的任务都被执行完成了。但是并行队列无法保证

执行下面代码:

let customConcurrentQueue = DispatchQueue(label: "custom", attributes: .concurrent)
customConcurrentQueue.async {
    print("当前线程 ->  thread: \(Thread.current)")
    customConcurrentQueue.sync {
        print("当前线程 ->  thread: \(Thread.current)")
    }
}
输出:
当前线程 ->  thread: <NSThread: 0x6000000b9a80>{number = 8, name = (null)}
当前线程 ->  thread: <NSThread: 0x6000000b9a80>{number = 8, name = (null)}

可以看到,并行队列会把同步任务安排到当前线程里执行

执行下面的代码:

let customSerialQueue = DispatchQueue(label: "serial")  // 自定义一个串行队列
let customQueue2 = DispatchQueue(label: "queue2", attributes: .concurrent)  // 自定义一个并行队列
customSerialQueue.async {
    print("当前线程A ->  thread: \(Thread.current)")
    customQueue2.async(execute: item1)
    customQueue2.async(execute: item2)
}

customSerialQueue.async {
    print("当前线程B ->  thread: \(Thread.current)")
    customQueue2.async(execute: item3)
    customQueue2.async(execute: item4)
}

输出:
当前线程A ->  thread: <NSThread: 0x600003750880>{number = 7, name = (null)}
当前线程B ->  thread: <NSThread: 0x600003750880>{number = 7, name = (null)}
item2 -> 0 thread: <NSThread: 0x600003746180>{number = 3, name = (null)}
item3 -> 0 thread: <NSThread: 0x60000374ec00>{number = 8, name = (null)}
item2 -> 1 thread: <NSThread: 0x600003746180>{number = 3, name = (null)}
item3 -> 1 thread: <NSThread: 0x60000374ec00>{number = 8, name = (null)}
item2 -> 2 thread: <NSThread: 0x600003746180>{number = 3, name = (null)}
item3 -> 2 thread: <NSThread: 0x60000374ec00>{number = 8, name = (null)}
item1 -> 0 thread: <NSThread: 0x60000374c940>{number = 5, name = (null)}
item3 -> 3 thread: <NSThread: 0x60000374ec00>{number = 8, name = (null)}
item2 -> 3 thread: <NSThread: 0x600003746180>{number = 3, name = (null)}
item3 -> 4 thread: <NSThread: 0x60000374ec00>{number = 8, name = (null)}
item2 -> 4 thread: <NSThread: 0x600003746180>{number = 3, name = (null)}
item1 -> 1 thread: <NSThread: 0x60000374c940>{number = 5, name = (null)}
item4 -> 0 thread: <NSThread: 0x600003750880>{number = 7, name = (null)}
item1 -> 2 thread: <NSThread: 0x60000374c940>{number = 5, name = (null)}
item4 -> 1 thread: <NSThread: 0x600003750880>{number = 7, name = (null)}
item1 -> 3 thread: <NSThread: 0x60000374c940>{number = 5, name = (null)}
item1 -> 4 thread: <NSThread: 0x60000374c940>{number = 5, name = (null)}
item4 -> 2 thread: <NSThread: 0x600003750880>{number = 7, name = (null)}
item4 -> 3 thread: <NSThread: 0x600003750880>{number = 7, name = (null)}
item4 -> 4 thread: <NSThread: 0x600003750880>{number = 7, name = (null)}

可以看到,并行队列开启了四个线程去分别执行四个任务,导致了四个任务的执行时间和结束时间是随机的。四个任务能够同时进行,下一个任务不必等上一个任务完成就能被分配出去

如果把 customQueue2 改成串行队列:

let customSerialQueue = DispatchQueue(label: "serial")  // 自定义一个串行队列
let customQueue2 = DispatchQueue(label: "queue2")
customSerialQueue.async {
    print("当前线程A ->  thread: \(Thread.current)")
    customQueue2.async(execute: item1)
    customQueue2.async(execute: item2)
}

customSerialQueue.async {
    print("当前线程B ->  thread: \(Thread.current)")
    customQueue2.async(execute: item3)
    customQueue2.async(execute: item4)
}

输出:
当前线程A ->  thread: <NSThread: 0x600001fc4700>{number = 8, name = (null)}
当前线程B ->  thread: <NSThread: 0x600001fc4700>{number = 8, name = (null)}
item1 -> 0 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item1 -> 1 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item1 -> 2 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item1 -> 3 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item1 -> 4 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item2 -> 0 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item2 -> 1 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item2 -> 2 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item2 -> 3 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item2 -> 4 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item3 -> 0 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item3 -> 1 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item3 -> 2 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item3 -> 3 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item3 -> 4 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item4 -> 0 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item4 -> 1 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item4 -> 2 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item4 -> 3 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}
item4 -> 4 thread: <NSThread: 0x600001ff4400>{number = 5, name = (null)}

可以看到,串行队列虽然能开启新的线程,但是它只开启了一个线程,保证了任务执行和结束的顺序是固定的

从队列中分配出去的任务最终还是要线程来执行的。如果没有线程来接受这个任务,那这个任务或许会一直不被分配出去。而并行队列和串行队列的区分,应该是很大程度上取决于他们开辟线程的能力以及能够开辟线程的个数

线程和调度队列

调度队列 (DispatchQueue)

首先需要明确,线程 ≠ 队列。如果把对各项任务的处理比做一个工厂的话,那么线程是处理任务的机器,而队列则是将记录任务并将任务分配给机器的人

队列遵循先进先出 (First in First out, FIFO) 规则,可以保证在执行某个任务时,排他前面的任务肯定都被执行了。队列有串行和并行之分,对于串行队列,GCD会指定一条线程来执行队列中的任务;对于并行队列,GCD会分配多条线程来执行其中的任务

队列有三种类型:主队列、全局队列、自定义队列

主队列 (Main Queue)

程序运行的当前队列就是主队列,是唯一的。主队列与主线程相关联,主队列上的任务运行在主线程上。与UI相关的操作也必须放在主队列上。通过下述代码获取主队列:

let mainQueue = DispatchQueue.main
全局队列 (Global Queue)

全局队列是系统放在后台供所有 App 共享使用的队列,是并行队列。获取方式是

let globalQueue = DispatchQueue.global()
自定义队列 (Custom Queue)

自定义队列默认是串行队列。若初始化时指定 attributes 参数为 concurrent,则可创建并行队列

let customSerialQueue = DispatchQueue(label: "serial")  // 自定义队列,默认为串行

let customQueue = DispatchQueue(label: "concurrent", attributes: .concurrent)   // 自定义并行队列

let customQoSQueue = DispatchQueue(label: "", qos: .userInteractive, attributes: .concurrent)   // 自定义并行队列,并指定队列优先级

参数含义分别是:

  • label: 队列标识,一般设置为 com.domain.somename,用于区分和调试
  • attributes: 队列属性,默认为串行。concurrent为并发队列。initiallyInactive表示该队列不会自动执行,需要开发者调用activate()(开始执行)、suspend()(挂起)、resume()(继续执行)手动管理相应的状态
  • qos: 即 Quality of Service, 是出于能效考虑而设置的优先级,一般走默认即可。qos有以下几种可选项(优先级从高到低):
  • userInteractive: 与用户交互有关的任务,优先级最高
  • userInitiated: 用户发起的任务,优先级比较高
  • default: 默认优先级。全局队列就是这个优先级
  • utility: 工具类型的任务,不需要得到立即响应。
  • background: 后台任务、不太重要的任务,优先级比较低
  • unspecified: 不确定的任务,优先级最低。这个优先级的任务有可能得不到处理

QoS 的设置可以参考:Energy Efficiency Guide for iOS Apps

调度任务 (DispatchWorkItem)

把要执行的代码写成一个闭包,赋给一个变量,方便管理任务。初始化代码如下:

// 走默认配置
let workItem1 = DispatchWorkItem {
    // some code
    ...
}

// 自定义配置
let workItem2 = DispatchWorkItem(qos: .userInteractive, flags: .barrier) {
    // some code
    ...
}

其中初始化参数 qos 与 DispatchQueue 类似。

「当前线程」

在本文中,「当前线程」指执行代码的线程,比如执行以下代码都是在「当前线程」执行的

print("hello world")

for index in 1...4 {
    ...
}

DispatchQueue.main.async{
    // 这里面的代码就不一定执行在当前线程了
    ...
}

DispatchQueue.global().async(execute: item)

注意,DispatchQueue.main.async{} 这个外层代码是在「当前线程」执行的,但是里面的 block 就不一定是在「当前线程」执行的了,因为它有可能已经被分配给别的线程了

「当前线程」一般是主线程,除非自己开辟了新的线程,比如:

print(">>> 当前线程是主线程 ->  thread: \(Thread.current)")
let myThread = Thread {
    print(">>> 当前线程不是主线程 ->  thread: \(Thread.current)")
}
myThread.start()

任务队列的分配调度依赖「当前线程」。DispatchQueue 队列里面任务的分配调度肯定是「当前线程」执行的,但是分配出去的任务不一定是当前线程执行的。就好比指挥官负责把一个个任务分配给某个士兵,这个士兵负责完成分配给他的任务(当然,指挥官也会给自己分配任务)

执行以下代码

// 这几个 item 后面也会用到
let item1 = DispatchWorkItem {
    for i in 0...4 {
        print("item1 -> \(i) thread: \(Thread.current)")
    }
}

let item2 = DispatchWorkItem {
    for i in 0...4 {
        print("item2 -> \(i) thread: \(Thread.current)")
    }
}

let item3 = DispatchWorkItem {
    for i in 0...4 {
        print("item3 -> \(i) thread: \(Thread.current)")
    }
}

let item4 = DispatchWorkItem {
    for i in 0...4 {
        print("item4 -> \(i) thread: \(Thread.current)")
    }
}

print("当前线程 ->  thread: \(Thread.current)")

let customQueue = DispatchQueue(label: "custom", attributes: .concurrent)
customQueue.async(execute: item1)
customQueue.sync(execute: item2)
customQueue.async(execute: item3)

// 输出:
当前线程 ->  thread: <_NSMainThread: 0x14df07b10>{number = 1, name = main}
item2 -> 0 thread: <_NSMainThread: 0x14df07b10>{number = 1, name = main}
item1 -> 0 thread: <NSThread: 0x14f107580>{number = 2, name = (null)}
item2 -> 1 thread: <_NSMainThread: 0x14df07b10>{number = 1, name = main}
item2 -> 2 thread: <_NSMainThread: 0x14df07b10>{number = 1, name = main}
item1 -> 1 thread: <NSThread: 0x14f107580>{number = 2, name = (null)}
item2 -> 3 thread: <_NSMainThread: 0x14df07b10>{number = 1, name = main}
item1 -> 2 thread: <NSThread: 0x14f107580>{number = 2, name = (null)}
item2 -> 4 thread: <_NSMainThread: 0x14df07b10>{number = 1, name = main}
item1 -> 3 thread: <NSThread: 0x14f107580>{number = 2, name = (null)}
item1 -> 4 thread: <NSThread: 0x14f107580>{number = 2, name = (null)}
item3 -> 0 thread: <NSThread: 0x14f107580>{number = 2, name = (null)}
item3 -> 1 thread: <NSThread: 0x14f107580>{number = 2, name = (null)}
item3 -> 2 thread: <NSThread: 0x14f107580>{number = 2, name = (null)}
item3 -> 3 thread: <NSThread: 0x14f107580>{number = 2, name = (null)}
item3 -> 4 thread: <NSThread: 0x14f107580>{number = 2, name = (null)}

可以看到,「当前线程」是主线程,item1 是异步任务,分配给了一个非主线程来执行;item2 是一个同步任务,分给了当前线程——主线程来执行。由于当前线程优先处理同步任务,所以此时当前线程被阻塞来处理 item2。item1 和 item2 分别由两个线程同时处理。而由于当前线程在处理 item2 的时候,还没有执行到customQueue.async(execute: item3)这行代码,所以 item3 没有被分配出去,也就没有被执行。等当前线程处理完 item2 的时候,才会把 item3 分配出去

再执行下一段代码:

let ct = Thread {
    print("当前线程 ->  thread: \(Thread.current)")
    customQueue.async(execute: item1)
    customQueue.sync(execute: item4)
    customQueue.async(execute: item2)
}
ct.start()

// 输出
当前线程 ->  thread: <NSThread: 0x14475a510>{number = 5, name = (null)}
item4 -> 0 thread: <NSThread: 0x14475a510>{number = 5, name = (null)}
item1 -> 0 thread: <NSThread: 0x14472a0f0>{number = 4, name = (null)}
item4 -> 1 thread: <NSThread: 0x14475a510>{number = 5, name = (null)}
item1 -> 1 thread: <NSThread: 0x14472a0f0>{number = 4, name = (null)}
item4 -> 2 thread: <NSThread: 0x14475a510>{number = 5, name = (null)}
item1 -> 2 thread: <NSThread: 0x14472a0f0>{number = 4, name = (null)}
item4 -> 3 thread: <NSThread: 0x14475a510>{number = 5, name = (null)}
item1 -> 3 thread: <NSThread: 0x14472a0f0>{number = 4, name = (null)}
item4 -> 4 thread: <NSThread: 0x14475a510>{number = 5, name = (null)}
item1 -> 4 thread: <NSThread: 0x14472a0f0>{number = 4, name = (null)}
item2 -> 0 thread: <NSThread: 0x144756780>{number = 6, name = (null)}
item2 -> 1 thread: <NSThread: 0x144756780>{number = 6, name = (null)}
item2 -> 2 thread: <NSThread: 0x144756780>{number = 6, name = (null)}
item2 -> 3 thread: <NSThread: 0x144756780>{number = 6, name = (null)}
item2 -> 4 thread: <NSThread: 0x144756780>{number = 6, name = (null)}

可以看到,「当前线程」非主线程,而是一个手动开辟的线程。item1、item4 和 item2 的处理方式与上面运行在主线程的代码类似。同步任务 item4 同样交给了当前线程来执行,item1 和 item2 由另外两个线程来处理。虽然当前线程并不是主线程,但它仍然在执行 item4 的时候被阻塞了,导致 item2 在 item4 执行完之后才被分配给另一个线程处理

基本使用

DispatchQueue 的同步/异步执行

取四种队列:

// 主队列
let mainQueue = DispatchQueue.main
// 全局队列
let globalQueue = DispatchQueue.global()
// 自定义并行队列
let customConcurrentQueue = DispatchQueue(label: "concurrent", attributes: .concurrent)
// 自定义串行队列
let customSerialQueue = DispatchQueue(label: "serial")

对上述四个队列分别调用同步和异步方法执行 item1 ~ item4:

// 同步执行
someQueue.sync(execute: item1)
someQueue.sync(execute: item2)
someQueue.sync(execute: item3)
someQueue.sync(execute: item4)

// 异步执行
someQueue.async(execute: item1)
someQueue.async(execute: item2)
someQueue.async(execute: item3)
someQueue.async(execute: item4)

结果如下:

  同步执行 异步执行
主队列 如果当前线程是主线程,会死锁;
否则的话,按顺序打印
按顺序执行
全局队列 在当前线程按顺序打印 随机打印
自定义串行队列 在当前线程按顺序打印 按顺序打印
自定义并行队列 在当前线程按顺序打印 随机打印

DispatchGroup 的使用

调度组(DispatchGroup)可以用来管理多个队列的异步任务,常用方法有 notify(), enter()leave()

场景一:多个网络接口同时开始请求,请求都结束后通知主队列刷新 UI

执行下面代码:

let group = DispatchGroup()
let queue1 = DispatchQueue(label: "request1", attributes: .concurrent)
let queue2 = DispatchQueue(label: "request2", attributes: .concurrent)

queue1.async(group: group) {
    print("接口一开始请求")
    sleep(3)
    print("接口一请求成功")
}

queue2.async(group: group) {
    print("接口二开始请求")
    sleep(3)
    print("接口二请求成功")
}

//主队列刷新UI
group.notify(queue: DispatchQueue.main) {
    print("UI刷新成功 当前线程: \(Thread.current)")
}

输出:
接口一开始请求
接口二开始请求
接口二请求成功
接口一请求成功
UI刷新成功 当前线程: <_NSMainThread: 0x6000006cc4c0>{number = 1, name = main}

队列一和二模拟了两个网络接口的请求。当请求都结束后,通知到主线程刷新 UI

场景二:多个网络接口按顺序请求,请求结束后通知主队列刷新 UI

现有两个接口,接口二依赖于接口一,只有当接口一请求完成后,接口二才能开始请求

代码如下:

let group = DispatchGroup()
let queue1 = DispatchQueue(label: "request1", attributes: .concurrent)
let queue2 = DispatchQueue(label: "request2", attributes: .concurrent)

group.enter()
queue1.async {
    print("接口一开始请求")
    sleep(3)
    print("接口一请求成功")
    group.leave()
}

group.wait()
group.enter()
queue2.async {
    print("接口二开始请求")
    sleep(3)
    print("接口二请求成功")
    group.leave()
}

//主队列刷新UI
group.notify(queue: DispatchQueue.main) {
    print("UI刷新成功 当前线程: \(Thread.current)")
}

输出:
接口一开始请求
接口一请求成功
接口二开始请求
接口二请求成功
UI刷新成功 当前线程: <_NSMainThread: 0x6000031544c0>{number = 1, name = main}

注意代码中 wait() 方法的使用。注意

queue.async(group: group) {
    // some code
}

可以写成

group.enter()
queue.async {
    // some code
    group.leave()
}

enterleave 必须成对出现

死锁分析

首先来分析一下,串行队列造成死锁的两种途径:

  1. 串行队列异步任务嵌套同步任务

下面这段代码在串行队列的一个异步任务中嵌套了一个同步任务,造成了死锁

// 自定义串行队列
let customSerialQueue = DispatchQueue(label: "serial")
customSerialQueue.async {
    print("当前线程 ->  thread: \(Thread.current)")
    customSerialQueue.sync {
        print("当前线程 ->  thread: \(Thread.current)")
    }
}

输出:
当前线程 ->  thread: <NSThread: 0x600003020840>{number = 7, name = (null)}

这段代码会运行第一个 print 函数,随后卡死。根据第一个 print 的输出可以看到,这个串行队列的异步任务放到了一个非主线程中执行。把第一个 async block 标记为任务A,里面的 sync block 标记为任务B。任务A被执行后,一个新的线程被创建出来了,A里面的代码会放在这个新线程里面执行。当执行到 sync 的时候,同步任务会分给当前线程执行,而且会阻塞当前线程。但是由于队列是串行队列,所以任务B被分出去的条件是任务A执行完了;而当前线程被阻塞了,要想执行完任务A,就得执行完任务B。这就导致了两个任务互相等待,造成死锁

  1. 串行队列同步任务嵌套同步任务

下面这段代码在串行队列的一个同步任务中嵌套了另一个同步任务,造成了死锁

// 自定义串行队列
let customSerialQueue = DispatchQueue(label: "serial")
customSerialQueue.sync {
    print("当前线程 ->  thread: \(Thread.current)")
    customSerialQueue.sync {
        print("当前线程 ->  thread: \(Thread.current)")
    }
}
输出:
当前线程 ->  thread: <_NSMainThread: 0x600001a044c0>{number = 1, name = main}

这段代码也会运行第一个 print 函数,随后卡死,并且 playground 会报错。由于同步任务会安排给当前线程执行,所以上面代码的第一个 sync 的 block 会分给主线程执行。由于串行队列只有在一个任务执行完成后才会分配下一个任务,所以当主线程执行完第一个 print 后,会告诉这个自定义串行队列去分配下一个同步任务(这时候主线程执行的是「分配队列任务」的任务,有点绕圈…)。但是又由于串行队列只能等上一个任务完成了才能分配下一个任务,但是下一个任务又包含在上一个任务里面。下一个任务是个同步任务,只能给当前线程执行,而且还会阻塞当前线程。上一个任务永远不会完成,下一个任务永远不会分配出去。这样就造成了两个任务互相等待,从而死锁

由于主队列是一个特殊的串行队列,所以上述对串行队列死锁的分析同样适用于主队列

之前在某篇文章上看到这句话:「在主队列中不能混入同步任务,否则会引起死锁。」❌
这句话是不对的,或者说是不完整的。正确的应该是,当前线程是主线程时,在主队列上混入同步任务会造成死锁

运行下面这段代码:

let ct = Thread {
    print("当前线程 ->  thread: \(Thread.current)")
    mainQueue.sync(execute: item2)
}
ct.start()

输出:
当前线程 ->  thread: <NSThread: 0x6000015d1500>{number = 8, name = (null)}
item2 -> 0 thread: <_NSMainThread: 0x6000015f04c0>{number = 1, name = main}
item2 -> 1 thread: <_NSMainThread: 0x6000015f04c0>{number = 1, name = main}
item2 -> 2 thread: <_NSMainThread: 0x6000015f04c0>{number = 1, name = main}
item2 -> 3 thread: <_NSMainThread: 0x6000015f04c0>{number = 1, name = main}
item2 -> 4 thread: <_NSMainThread: 0x6000015f04c0>{number = 1, name = main}

上面代码在主队列安排了一个同步任务,但是没有死锁。这是因为在 Thread{} 里面,当前线程不是主线程,所以主队列混入同步任务没有造成死锁

那么问题来了,为什么串行队列嵌套异步任务不会引起死锁? 为什么并行队列嵌套同步任务不会引起死锁?

分析如下:

  1. 串行队列嵌套异步任务

执行下面代码

let customSerialQueue = DispatchQueue(label: "serial")
customSerialQueue.sync {
    print("当前线程 ->  thread: \(Thread.current)")
    customSerialQueue.async {
        print("当前线程 ->  thread: \(Thread.current)")
    }
}

输出:
当前线程 ->  thread: <_NSMainThread: 0x600003ab04c0>{number = 1, name = main}
当前线程 ->  thread: <NSThread: 0x600003a90600>{number = 8, name = (null)}

可以看到,里层嵌套的异步任务是在新开辟的线程上执行的,而且异步任务不需要等待里面的代码执行完就能返回,不会造成任务间的互相等待,所以不会有死锁产生

  1. 并行队列嵌套同步任务

执行下面的代码

let customQueue = DispatchQueue(label: "custom", attributes: .concurrent)
customQueue.async {
    print("当前线程 ->  thread: \(Thread.current)")
    customQueue.sync {
        print("当前线程 ->  thread: \(Thread.current)")
    }
}

输出:
当前线程 ->  thread: <NSThread: 0x6000028d5940>{number = 4, name = (null)}
当前线程 ->  thread: <NSThread: 0x6000028d5940>{number = 4, name = (null)}

里层嵌套的同步任务同样是在一个新开辟的线程上进行的,新线程执行完第一个 print 后,接着执行嵌套的 sync block。由于队列是并行队列,所以里面的同步任务不需要等外面的任务结束就能分配出去,相当于内层代码按顺序执行了,不会产生死锁

Read more

碎碎念——投资,不确定性沟通定语

碎碎念——投资,不确定性沟通定语

投资理财 最近因为关税的冲击,美股正在经历一波大跌行情。我个人比较看好纳斯达克,也在一直定投纳斯达克。我是长期主义者,没有精力和时间在短期波动中挣钱,只想在下跌调整中「进货」。 定投分左侧定投和右侧定投。左侧定投是在下跌的过程中定投,而右侧定投是在上涨的过程中定投。左侧定投无法确认底部在哪里,需要源源不断往里投入金钱(行内成为「子弹」);右侧定投无法确认反弹是诱多还是形势已经逆转。我采用的是左侧定投,大跌大加,小跌小加,反弹时停止定投。不论采用哪种定投,殊途同归,都是尽量降低投资成本。 目前网上看衰美股的声音不少,不少人因为恐慌割肉卖出股票。但我们要知道目前美国仍旧是世界第一大国,消费潜力巨大,大型科技公司(苹果、英伟达等)的基本面并没有出现大问题。只是因为特朗普的「量子态」关税政策,导致市场恐慌抛售。我们无需担心纳斯达克、标普指数从此一蹶不振。恰恰相反,现在是买入美股的绝佳时机。苹果、英伟达等大型公司的 PE 值已经降到了合理位置,只要不买妖股,不投机,只关注纳斯达克、标普指数,只买大型公司股票,迟早会取得丰厚盈利的。

By Gray
怀念小时候吃过的食物

怀念小时候吃过的食物

前两天下班骑车回家的路上听到了路旁有人在讨论泡馍。他们口中的泡馍应该是类似西安羊肉泡馍之类的食物。但是我却想起来了小时候吃的不一样的泡馍以及其他吃食。 不一样的泡馍 小时候我们那里普遍比较贫穷,家家户户除了过年过节基本上很难吃到大块肉。小孩子饭量时小时大,中午吃的饭,半晌就又饿了。家里有大葱或者豆糁的话,可以拿着一个馍就着就吃了。整根的葱是最下馍的,葱白部分甜又辣,葱叶里面会有像鼻涕一样的粘液,要把它挤出来才下得嘴吃。豆糁是黄豆的发酵产物,煮熟的大豆加盐发酵几天,黏丝丝的时候团成球,放到发黑就能吃了。吃的时候从球上掰下来几小块就行。豆糁是咸的,因而也能下饭。不过最妙的吃法是将豆糁和鸡蛋一起炒。鸡蛋的香气和豆糁稍微发臭的味道混在一起,形成一种独特的香味。像北京的臭豆腐一样,闻着臭,吃着香。 如果家里没葱没豆糁了,馍又很干,那泡馍就是解决饿肚子的绝好办法。将干硬的馍掰成几瓣,不能太碎小,放到瓷碗里。倒入炒菜的肉味王佐料,或者是平时攒下来的方便面调料。再提溜着暖水瓶,倒进去冒着热气的水。当然香油是少不了的,拿着油光光的瓶子,滴进去几滴喷香的香油。最后用大碗盖住,或者干脆啥也不盖,静等

By Gray
Swift Server Push Notification 配置

Swift Server Push Notification 配置

获取证书 在 Apple Developer 开发者账号 Certificates, Identifiers & Profiles 里选择 Keys。新增一个 key, configure 里选择 Sandbox & Production。下载该 p8 证书,并且保存好(只能下载一次)。 终端 cd 到证书所在路径,输入下面指令。 openssl pkcs8 -nocrypt -in AuthKey_XXXXXXXXX.p8 -out ~/Downloads/key.pem cat key.pem 得到 PRIVATE KEY 字符串,复制好。 服务端配置 服务端有多种技术栈方案,包括 Java、

By Gray
香港游记——一个传统而又现代的城市

香港游记——一个传统而又现代的城市

这是 2024 年的最后一场旅行,从北京到香港,跨越了大半个中国。去香港,一方面是想领略一下它的文化和风光,另一方面是想办一个香港银行卡,买港美股以及海外收付款。 从北京到香港,动卧是一个不错的选择。乘坐 D903 次动车,晚上八点登车,睡一觉,第二天一早就到深圳北了。再从深圳北坐高铁过口岸到香港西九龙,差不多上午九点多就能到达香港。深圳北到西九龙的高铁车次非常多,不用担心买不到票。 密集的建筑 香港给我的初印象就是——这里的楼房真的很密集。不光是住宅区又高又密,商业区的建筑物与建筑物之间也几乎只有街道相隔,很少见到大型的公园或者绿化带。土地利用率很高。这一点和北京差别还是挺大的。北京虽然也是寸土寸金,但是市内绿化面积很高,大型公园也很常见。 街上密集的建筑,让人第一眼看就知道这是香港。 旧与新,传统与现代 在香港,不同地区的风格面貌会相差很多。你既能见到破旧不堪、需要修缮的古老楼房,也能见到银光闪闪、科技感十足的现代化大厦。这种新与旧的切换,传统和现代的反差,总是能给人强烈的震撼。这正是香港的魅力所在。 维多利亚港和中环摩天轮 维多利亚港是香港的中心,是香港旅游

By Gray