容易混淆的术语:同步 异步 串行 并发
同步:
sync
函数
在当前线程中执行任务,不具备开启新线程的能力异步:
async
函数
在新的线程中执行任务,具备开启新线程的能力同步
和异步
主要影响:能不能开启新的线程(是否在当前线程执行任务)并发
和串行
主要影响:任务的执行方式
并发
:多个任务并发(同时)执行
串行
:一个任务执行完毕后,再执行下一个任务
sync和async用来控制是否要开启新的线程.队列的类型,决定了任务的执行方式(并发 串行). async只表示具有开启新线程的能力,但不一定开启新的线程.比如async传入主队列不会开启新的线程.主队列是在主线程执行.
-
sync
同步函数向主队列
添加任务会走造成死锁.
以下代码输出结果是什么?为什么?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.blue
let queue = DispatchQueue.main
queue.sync {
print(1)
}
}
viewDidLoad
方法本身就是主线程的一个任务.viewDidLoad
这个任务是先添加进主线程的一个任务,需要先将viewDidLoad
这个任务执行完,才能执行queue.sync
任务.但queue.sync
是后后添加的任务,需要等上一个任务viewDidLoad
执行完才能执行,所以构成死锁.
- 将
sync
函数换成async
函数,还是在主队列,不会死锁,async
函数不会等待任务执行完,会直接向下执行.async
函数不要求立刻执行.async
函数具备开启新现成的能力,但是在主线程执行任务,所以不会开启新的线程.
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.blue
print(1111)
let queue = DispatchQueue.main
queue.async {
print(22222)
}
print(3333333)
}
//打印结果
1111
3333333
22222
自定义并发队列
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.blue
//这是一个并发队列
let serialQueue = DispatchQueue.init(label: "", qos: .default, attributes: [.concurrent], autoreleaseFrequency: .inherit, target: nil)
print(1111,Thread.current)
serialQueue.async {
print(2222,Thread.current)
serialQueue.sync {
print(33333,Thread.current)
}
print(4444444444,Thread.current)
serialQueue.sync {
print(555555555,Thread.current)
}
print(666666666,Thread.current)
}
print(77777777,Thread.current)
}
//打印结果
//注意:22的打印可能介于11和77之间,因为`async`函数不要求立刻执行,什么时候执行不确定.有可能22执行结束优先于777
1111 <NSThread: 0x600000d0a2c0>{number = 1, name = main}
77777777 <NSThread: 0x600000d0a2c0>{number = 1, name = main}
2222 <NSThread: 0x600000d88440>{number = 7, name = (null)}
33333 <NSThread: 0x600000d88440>{number = 7, name = (null)}
4444444444 <NSThread: 0x600000d88440>{number = 7, name = (null)}
555555555 <NSThread: 0x600000d88440>{number = 7, name = (null)}
666666666 <NSThread: 0x600000d88440>{number = 7, name = (null)}
自定义串行队列
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.blue
//这是一个并发队列
let serialQueue = DispatchQueue(label: "自定义串行队列")
print(11111)
serialQueue.async {
print(22222)
//往串行队列中添加同步(立刻执行的任务会造成死锁)
serialQueue.sync {
print(3333)
}
print(4444)
}
print(5555)
}
//打印结果
//理论上22222的打印可能介于111和555之间
11111
5555
22222
4444
3333
死锁产生条件
-
sync
函数往当前串行队列中添加任务就会造成死锁
RunLoop和多线程相关问题
如下代码输出什么?为什么?
class HomeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let queue = DispatchQueue.global()
queue.async {
print(1)
self.perform(#selector(self.test), with: nil, afterDelay: 0)
print(2)
}
}
@objc func test() {
print("test")
}
}
//打印,没有看到test方法执行
1
2
-
perform(Selector, with: Any?, afterDelay: TimeInterval)
本质是往RunLoop
中添加定时器NSTimer
,子线程默认没有启动RunLoop
. - 如果想让上述代码工作,需要在子线程添加
RunLoop
并启动 -
perform(Selector, with: Any?, afterDelay: TimeInterval)
是在runLoop
中定义的方法. -
self.perform(Selector!, with: Any!)
底层是objc_messageSend
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.blue
let queue = DispatchQueue.global()
queue.async {
print(111)
self.perform(#selector(self.test), with: nil, afterDelay: 0.0)
print(333)
//在子线程中添加runloop
let port = Port()
//perform(#selector(self.test), with: nil, afterDelay: 0.0)
//方法已经在子线程的runloop中添加了NSTimer.所以不
//需要再添加 port,所以这句代码可以去掉
//runloop中只要有 source timer observer runloop就可以
//成功运行
RunLoop.current.add(port, forMode: .default)
//RunLoop.current.run()
RunLoop.current.run(mode: .default, before: Date.distantFuture)
}
}
//打印结果
111
333
2222
下面代码执行结果是什么?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let thread = Thread.init {
print(1)
}
thread.start()
self.perform(#selector(self.test), on: thread, with: nil, waitUntilDone: true)
}
@objc func test() {
print(2)
}
- 执行结果:打印1之后程序
crash
.因为线程执行thread.start()
后,该线程的任务就执行完成,线程就被销毁了,销毁之后又使用该线程所以造成了crash
.如果该线程里面启动了RunLoop
该线程就不会销毁,就会正常执行.代码如下:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let thread = Thread.init {
print(1)
//在runloop中添加source timer observer
RunLoop.current.add(Port(), forMode: .default)
//启动runloop
RunLoop.current.run()
}
thread.start()
self.perform(#selector(self.test), on: thread, with: nil, waitUntilDone: true)
}
@objc func test() {
print(2)
}
-
runloop
启动之后会等待该线程的任务.如果处理完该线程当前的任务,runloop
就会进入休眠状态.等待下一个任务的到来,如果下一个任务到来runloop
就会被激活.处理这个任务,当这个任务处理完成后runloop
会再次进入休眠状态.
队列组的使用
- 如何用GCD实现以下功能:异步并发执行任务1,任务2.等任务1和任务2执行完毕后,再回到主线程执行任务3.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//创建队列组
let group = DispatchGroup()
//创建并发队列
let queue = DispatchQueue.global()
queue.async(group: group, execute: {
for _ in 0...10{
print(1,"任务1",Thread.current)
}
})
queue.async(group: group, execute: {
for _ in 0...10{
print(2,"任务2",Thread.current)
}
})
group.notify(queue: queue) {
DispatchQueue.main.async {
for _ in 0...10{
print(3,"任务3",Thread.current)
}
}
}
}
-
可以看到任务1和任务2在子线程交替执行,执行完成后再执行任务3.
- 最后回到主线程执行任务也可以通过
队列组
直接传入主队列
或者通过主队列
使用sync
函数
group.notify(queue: DispatchQueue.main) {
for _ in 0...10{
print(3,"任务3",Thread.current)
}
}
//或者
group.notify(queue: queue) {
DispatchQueue.main.sync {
for _ in 0...10{
print(3,"任务3",Thread.current)
}
}
- 如果想要办到任务1和任务2交替执行,等任务1和任务2都执行完成之后再交替执行任务3和任务4,那么任务3和任务4继续使用
group.notify
即可
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//创建队列组
let group = DispatchGroup()
//创建并发队列
let queue = DispatchQueue.global()
queue.async(group: group, execute: {
for _ in 0...5{
print(1,"任务1",Thread.current)
}
})
queue.async(group: group, execute: {
for _ in 0...5{
print(2,"任务2",Thread.current)
}
})
group.notify(queue: queue) {
for _ in 0...5{
print(3,"任务3",Thread.current)
}
}
group.notify(queue: queue) {
for _ in 0...5{
print(4,"任务4",Thread.current)
}
}
}