容易混淆的術(shù)語(yǔ):同步 異步 串行 并發(fā)
同步:
sync
函數(shù)
在當(dāng)前線程中執(zhí)行任務(wù),不具備開(kāi)啟新線程的能力異步:
async
函數(shù)
在新的線程中執(zhí)行任務(wù),具備開(kāi)啟新線程的能力同步
和異步
主要影響:能不能開(kāi)啟新的線程(是否在當(dāng)前線程執(zhí)行任務(wù))并發(fā)
和串行
主要影響:任務(wù)的執(zhí)行方式
并發(fā)
:多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行
串行
:一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù)
sync和async用來(lái)控制是否要開(kāi)啟新的線程.隊(duì)列的類(lèi)型,決定了任務(wù)的執(zhí)行方式(并發(fā) 串行). async只表示具有開(kāi)啟新線程的能力,但不一定開(kāi)啟新的線程.比如async傳入主隊(duì)列不會(huì)開(kāi)啟新的線程.主隊(duì)列是在主線程執(zhí)行.
-
sync
同步函數(shù)向主隊(duì)列
添加任務(wù)會(huì)走造成死鎖.
以下代碼輸出結(jié)果是什么?為什么?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.blue
let queue = DispatchQueue.main
queue.sync {
print(1)
}
}
viewDidLoad
方法本身就是主線程的一個(gè)任務(wù).viewDidLoad
這個(gè)任務(wù)是先添加進(jìn)主線程的一個(gè)任務(wù),需要先將viewDidLoad
這個(gè)任務(wù)執(zhí)行完,才能執(zhí)行queue.sync
任務(wù).但queue.sync
是后后添加的任務(wù),需要等上一個(gè)任務(wù)viewDidLoad
執(zhí)行完才能執(zhí)行,所以構(gòu)成死鎖.
- 將
sync
函數(shù)換成async
函數(shù),還是在主隊(duì)列,不會(huì)死鎖,async
函數(shù)不會(huì)等待任務(wù)執(zhí)行完,會(huì)直接向下執(zhí)行.async
函數(shù)不要求立刻執(zhí)行.async
函數(shù)具備開(kāi)啟新現(xiàn)成的能力,但是在主線程執(zhí)行任務(wù),所以不會(huì)開(kāi)啟新的線程.
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.blue
print(1111)
let queue = DispatchQueue.main
queue.async {
print(22222)
}
print(3333333)
}
//打印結(jié)果
1111
3333333
22222
自定義并發(fā)隊(duì)列
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.blue
//這是一個(gè)并發(fā)隊(duì)列
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)
}
//打印結(jié)果
//注意:22的打印可能介于11和77之間,因?yàn)閌async`函數(shù)不要求立刻執(zhí)行,什么時(shí)候執(zhí)行不確定.有可能22執(zhí)行結(jié)束優(yōu)先于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)}
自定義串行隊(duì)列
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.blue
//這是一個(gè)并發(fā)隊(duì)列
let serialQueue = DispatchQueue(label: "自定義串行隊(duì)列")
print(11111)
serialQueue.async {
print(22222)
//往串行隊(duì)列中添加同步(立刻執(zhí)行的任務(wù)會(huì)造成死鎖)
serialQueue.sync {
print(3333)
}
print(4444)
}
print(5555)
}
//打印結(jié)果
//理論上22222的打印可能介于111和555之間
11111
5555
22222
4444
3333
死鎖產(chǎn)生條件
-
sync
函數(shù)往當(dāng)前串行隊(duì)列中添加任務(wù)就會(huì)造成死鎖
RunLoop和多線程相關(guān)問(wèn)題
如下代碼輸出什么?為什么?
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")
}
}
//打印,沒(méi)有看到test方法執(zhí)行
1
2
-
perform(Selector, with: Any?, afterDelay: TimeInterval)
本質(zhì)是往RunLoop
中添加定時(shí)器NSTimer
,子線程默認(rèn)沒(méi)有啟動(dòng)RunLoop
. - 如果想讓上述代碼工作,需要在子線程添加
RunLoop
并啟動(dòng) -
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)
//方法已經(jīng)在子線程的runloop中添加了NSTimer.所以不
//需要再添加 port,所以這句代碼可以去掉
//runloop中只要有 source timer observer runloop就可以
//成功運(yùn)行
RunLoop.current.add(port, forMode: .default)
//RunLoop.current.run()
RunLoop.current.run(mode: .default, before: Date.distantFuture)
}
}
//打印結(jié)果
111
333
2222
下面代碼執(zhí)行結(jié)果是什么?
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)
}
- 執(zhí)行結(jié)果:打印1之后程序
crash
.因?yàn)榫€程執(zhí)行thread.start()
后,該線程的任務(wù)就執(zhí)行完成,線程就被銷(xiāo)毀了,銷(xiāo)毀之后又使用該線程所以造成了crash
.如果該線程里面啟動(dòng)了RunLoop
該線程就不會(huì)銷(xiāo)毀,就會(huì)正常執(zhí)行.代碼如下:
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)
//啟動(dòng)runloop
RunLoop.current.run()
}
thread.start()
self.perform(#selector(self.test), on: thread, with: nil, waitUntilDone: true)
}
@objc func test() {
print(2)
}
-
runloop
啟動(dòng)之后會(huì)等待該線程的任務(wù).如果處理完該線程當(dāng)前的任務(wù),runloop
就會(huì)進(jìn)入休眠狀態(tài).等待下一個(gè)任務(wù)的到來(lái),如果下一個(gè)任務(wù)到來(lái)runloop
就會(huì)被激活.處理這個(gè)任務(wù),當(dāng)這個(gè)任務(wù)處理完成后runloop
會(huì)再次進(jìn)入休眠狀態(tài).
隊(duì)列組的使用
- 如何用GCD實(shí)現(xiàn)以下功能:異步并發(fā)執(zhí)行任務(wù)1,任務(wù)2.等任務(wù)1和任務(wù)2執(zhí)行完畢后,再回到主線程執(zhí)行任務(wù)3.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//創(chuàng)建隊(duì)列組
let group = DispatchGroup()
//創(chuàng)建并發(fā)隊(duì)列
let queue = DispatchQueue.global()
queue.async(group: group, execute: {
for _ in 0...10{
print(1,"任務(wù)1",Thread.current)
}
})
queue.async(group: group, execute: {
for _ in 0...10{
print(2,"任務(wù)2",Thread.current)
}
})
group.notify(queue: queue) {
DispatchQueue.main.async {
for _ in 0...10{
print(3,"任務(wù)3",Thread.current)
}
}
}
}
-
可以看到任務(wù)1和任務(wù)2在子線程交替執(zhí)行,執(zhí)行完成后再執(zhí)行任務(wù)3.
- 最后回到主線程執(zhí)行任務(wù)也可以通過(guò)
隊(duì)列組
直接傳入主隊(duì)列
或者通過(guò)主隊(duì)列
使用sync
函數(shù)
group.notify(queue: DispatchQueue.main) {
for _ in 0...10{
print(3,"任務(wù)3",Thread.current)
}
}
//或者
group.notify(queue: queue) {
DispatchQueue.main.sync {
for _ in 0...10{
print(3,"任務(wù)3",Thread.current)
}
}
- 如果想要辦到任務(wù)1和任務(wù)2交替執(zhí)行,等任務(wù)1和任務(wù)2都執(zhí)行完成之后再交替執(zhí)行任務(wù)3和任務(wù)4,那么任務(wù)3和任務(wù)4繼續(xù)使用
group.notify
即可
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//創(chuàng)建隊(duì)列組
let group = DispatchGroup()
//創(chuàng)建并發(fā)隊(duì)列
let queue = DispatchQueue.global()
queue.async(group: group, execute: {
for _ in 0...5{
print(1,"任務(wù)1",Thread.current)
}
})
queue.async(group: group, execute: {
for _ in 0...5{
print(2,"任務(wù)2",Thread.current)
}
})
group.notify(queue: queue) {
for _ in 0...5{
print(3,"任務(wù)3",Thread.current)
}
}
group.notify(queue: queue) {
for _ in 0...5{
print(4,"任務(wù)4",Thread.current)
}
}
}