前言:
Dispatch(Grand Central Dispatch)(超級中二的命名
與pthread和Thread不同的是,GCD增加了兩個很重要的概念,任務(wù)和隊列,它是iOS多線程的核心框架.
任務(wù)(Work Item, source等
任務(wù)就是要執(zhí)行的代碼段,很容易理解,GCD提供了很多任務(wù)的形式,比如DispatchWorkItem,DispatchSource,DispatchGroup,或者是一個block中的代碼.隊列(queue
隊列指的是任務(wù)的隊列,或者叫調(diào)度隊列(Dispatch queue),但這個是抽象的概念,并不是數(shù)據(jù)結(jié)構(gòu)上的隊列,不僅可以存放任務(wù),還可以指定任務(wù)如何執(zhí)行,隊列有兩種,串行隊列DISPATCH_QUEUE_SERIAL和并發(fā)隊列DISPATCH_QUEUE_CONCURRENT.
隊列永遠(yuǎn)都是先進(jìn)先出,串行會等待前面的任務(wù)執(zhí)行完,再執(zhí)行下一個任務(wù),并發(fā)的主旨是讓任務(wù)一起執(zhí)行,具體能不能做到還有其他限制,但是并發(fā)仍然是先進(jìn)的先執(zhí)行,只不過不會等待前面的任務(wù)結(jié)束,并且無法得知某個任務(wù)什么時候開始,什么時候結(jié)束,這些都取決于GCD自身.同步和異步(sync和async
只有串行隊列和并發(fā)隊列還不能決定任務(wù)如何執(zhí)行,還需要指定是否創(chuàng)建線程,也就是指定同步sync還是異步async.同步不會開啟新的線程,異步一定會開啟新線程.
a.串行隊列指定任務(wù)需要一個個執(zhí)行,如果指定同步sync進(jìn)行這個隊列,則會在當(dāng)前的線程中執(zhí)行;
b.如果串行隊列指定異步async進(jìn)行,由于線程是cpu最小調(diào)度單位,沒辦法在兩個任務(wù)之間調(diào)度,那GCD只好開啟一個新的線程來進(jìn)行這個隊列,之后就和a是一樣的邏輯了;
c.并發(fā)隊列指定任務(wù)同時進(jìn)行,如果指定sync,那么就不會生效
d.如果并發(fā)隊列指定異步進(jìn)行,那么GCD會根據(jù)任務(wù)數(shù)量創(chuàng)建一個或多個新的線程同時執(zhí)行,當(dāng)然線程的數(shù)量還會受到GCD底層實(shí)現(xiàn)的限制,一方面如果線程太多,來一個任務(wù)就開一個線程,還要隊列干嘛,另一方面太多線程占用更多資源,一般最多是64個.
不管是主線程還是其他線程,一般都不會直接操作,需要關(guān)注的是任務(wù)本身,而管理任務(wù)的則是隊列,同步還是異步是任務(wù)的執(zhí)行策略,而執(zhí)行策略決定會不會開啟線程.
- 并發(fā)與并行:
并行指任務(wù)同時執(zhí)行,是系統(tǒng)的行為,并發(fā)指的是代碼的設(shè)計,希望代碼的不同部分能夠同時執(zhí)行,是不是真的能"同時"執(zhí)行取決于系統(tǒng),多核可以真正的同時執(zhí)行,而CPU自己也可以通過快速切換上下文來"同時"執(zhí)行多個任務(wù);雖然能寫出并發(fā)的代碼,但是是不是真的并行還要看GCD自己;并行的前提是并發(fā),并發(fā)不能保證并行.
線程是cpu調(diào)度的方式,cpu只能處理一個任務(wù),一個線程最多占用cpu幾毫秒的時間,之后cpu會調(diào)度到其他線程,如果任務(wù)沒執(zhí)行完,之后還會調(diào)度回來繼續(xù)執(zhí)行.
一:基本使用
本篇講的是swift的Dispatch庫,大多數(shù)東西和OC的Dispatch相差不大,但是也優(yōu)化了一些東西.
1.主線程(主隊列)
主線程是唯一能夠更新UI的線程,通常說的主線程其實(shí)指的是主隊列(DispatchQueue.main),它是一個串行隊列,里面的任務(wù)都會在主線程中串行執(zhí)行;
GCD使用線程池來管理線程, 除了主隊列只會在主線程進(jìn)行之外(一開始就創(chuàng)建好了),任何任務(wù)都不能確定在哪個線程上進(jìn)行;當(dāng)然可以在運(yùn)行的過程中查看當(dāng)前線程.
就像前面說的,同步和異步?jīng)Q定是否開啟線程,同步任務(wù)會在當(dāng)前線程等待別的任務(wù)完成,如果添加一個同步的任務(wù),并且指定在主隊列中,會造成主隊列的死鎖.
但是這個現(xiàn)象不是特例,這個過程可以簡化成這樣:
DispatchQueue.global().async {
let queue = DispatchQueue.init(label: "test")
queue.sync {//A
print("aaa")
queue.sync {//B
print("bbb")
}
}//c
}
為了和主線程區(qū)分開,這段代碼先創(chuàng)建了一個線程,假設(shè)叫T,里面的代碼不在主線程運(yùn)行;
然后創(chuàng)建了一個串行隊列queue,添加了一個同步的任務(wù)A,于是A會在線程T中執(zhí)行,在A中又添加了一個同步任務(wù)B到同一個隊列中,B會在A執(zhí)行完后再執(zhí)行,也就是走完c的位置,但是c的位置依賴于B的代碼走完,于是就堵在了這里;簡單來說就是A執(zhí)行了一半把B塞了進(jìn)去,他倆在同一個線程中誰都不能先完成.
再重新思考主隊列的死鎖,如果同步任務(wù)的代碼寫在viewDidLoad里,那么主隊列的任務(wù)要在viewDidLoad至少走完才會結(jié)束,添加在viewDidLoad中間的同步任務(wù)和viewDidLoad本身互相等待造成死鎖.
DispatchQueue.global().async {
let queue = DispatchQueue.init(label: "test")
queue.sync {//A
print("aaa")
}//c
queue.sync {//B
print("bbb")
}
}
如果把任務(wù)B放在c外面,就沒有問題
DispatchQueue.global().async {
let queue = DispatchQueue.init(label: "test")
queue.sync {//A
print(Thread.current)
let queue2 = DispatchQueue.init(label: "test")
queue2.sync {//B
print(Thread.current)
}
}//c
}
如果把B放在另一個隊列queue2,也不會有問題,A和B仍然都在線程T中執(zhí)行(兩個print輸出的結(jié)果一樣),會按照添加的順序執(zhí)行.
DispatchQueue.global().async {
let queue = DispatchQueue.init(label: "test")
queue.sync {//A
print(Thread.current)
queue.async{//B
print(Thread.current)
}
}//c
}
再或者把B異步添加到隊列queue,也能正常執(zhí)行,這時gcd會開啟新線程,兩個print結(jié)果不一樣.
DispatchQueue.global().async {
print("0.\(Thread.current)")
let queue = DispatchQueue.init(label: "test", qos: .background, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)
queue.sync {//A
print("1.\(Thread.current)")
queue.sync{//B
print("2.\(Thread.current)")
}
}//c
}
最后一種,把queue換成并發(fā)隊列,同步執(zhí)行不會開啟線程,0,1,2都是同一個線程,但是不會死鎖,并發(fā)隊列可以看出多軌道,但是線程只能只能執(zhí)行一個任務(wù),如果只有一個線程,就只好一個個的執(zhí)行,但是不會造成第一個例子的那種阻塞.
總結(jié)一下就是,同步串行再加上任務(wù)重疊,就會造成死鎖
- 前言說到串行隊列添加異步任務(wù)會開啟新的線程,但是主線程例外,主隊列的任務(wù)只會運(yùn)行在主線程.
DispatchQueue.init(label: "q1").async {
print("aaa -- \(Thread.current)")
}
DispatchQueue.main.async {
print("bbb -- \(Thread.current)")
}
DispatchQueue.init(label: "q2").async {
print("ccc -- \(Thread.current)")
}
aaa和ccc會開啟新的線程,而bbb不會,并且子線程的執(zhí)行速度還更快,因?yàn)閙ain.async里的任務(wù)要等viewDidLoad走完.
DispatchQueue.main.async {
print("1:\(Thread.current)")
let queue = DispatchQueue.init(label: "q2")
queue.sync {
print("2:\(Thread.current)")
}
}
print("3:\(Thread.current)")
可以看到3先輸出,也就是viewDidLoad先走完了,之后1和2相繼執(zhí)行.
2.全局隊列
GCD隊列黔州,系統(tǒng)隊列編號有11個尿招,1為主隊列,2為管理隊列,3保留度苔;4-11為8個全局隊列伪货,有四種優(yōu)先級(quality-of-service) ,對應(yīng)文檔里從上到下是優(yōu)先級從高到低,不過還有一個default和一個暫不指定unspecified
這些隊列是系統(tǒng)提供的,初始化其實(shí)是獲取而非創(chuàng)建,而且可能會有系統(tǒng)的任務(wù)在里面,因此開發(fā)者的代碼不是其中唯一的任務(wù).
DispatchQueue.global(),可以獲取一個默認(rèn)的全局隊列.
DispatchQueue.global(qos:),獲取一個指定優(yōu)先級的全局隊列
let queue2 = DispatchQueue.global(qos: DispatchQoS.QoSClass.background)
queue2.async {
print("2.\(Thread.current)")
}
let queue = DispatchQueue.global(qos:DispatchQoS.QoSClass.userInteractive)
queue.async {
print("1.\(Thread.current)")
}
不同優(yōu)先級的全局隊列,cpu調(diào)度的優(yōu)先度不同,即便1寫在后面,也是先執(zhí)行
3.自定義隊列
事實(shí)上,自定義的隊列最終都會指向系統(tǒng)創(chuàng)建的隊列,雖然是init,但其實(shí)是獲取已經(jīng)存在的或者系統(tǒng)在需要的時候創(chuàng)建的隊列,之所以這么做,是為了方便管理任務(wù),或者說給系統(tǒng)隊列取個別名.
let queue = DispatchQueue.init(label: "test")
自定義一個串行隊列
let queue = DispatchQueue.init(label: "test", qos: .background, attributes: .concurrent, autoreleaseFrequency: . workItem, target: nil)
自定義一個并發(fā)隊列
- label是取個名字
- qos參數(shù)和獲取全局隊列是一樣的,
- attributes有兩個值, concurrent(創(chuàng)建一個并發(fā)隊列), initiallyInactive(需要手動調(diào)用activate觸發(fā)),如果傳nil,則返回串行隊列,并且可以傳數(shù)組[concurrent,initiallyInactive]
- autoreleaseFrequency是任務(wù)相關(guān)的自動釋放,有三個值,inherit跟隨后面的target隊列,后面再說; workItem按照任務(wù)的周期,取決于任務(wù)本身; never不主動釋放,需要手動管理
- target是獲取已存在的隊列,這個目標(biāo)隊列決定了最終返回的隊列的屬性.
4.任務(wù)運(yùn)行在哪個線程
前言提到了隊列是開發(fā)者關(guān)注的重點(diǎn),隊列的屬性是串行或者并發(fā);
同步還是異步是任務(wù)的執(zhí)行策略,這其中還好包含優(yōu)先級等屬性,在下一篇會說到;
let queue = DispatchQueue.init(label: "aaa")
for i in 0 ..< 100{
if i % 2 == 0{
queue.async {
sleep(2)
print("\(i): even -- async -- \(Thread.current)")
}
}else{
queue.sync {
sleep(1)
print("\(i): uneven -- sync -- \(Thread.current)")
}
}
}
這個例子創(chuàng)建一個串行隊列,循環(huán)100次,偶數(shù)時向隊列添加異步任務(wù),奇數(shù)添加同步任務(wù),看一下控制臺輸出
可以看到一定是按照順序輸出的,串行隊列規(guī)定后添加的任務(wù)要在前面的任務(wù)完成后才開始,同步和異步只決定是否開啟新的線程,并且可以看到同步的任務(wù)當(dāng)前線程執(zhí)行(這里是主線程),異步的在其他線程,具體是哪一個線程,由GCD決定,每一次的運(yùn)行不一定會相同.
override func viewDidLoad() {
super.viewDidLoad()
let queue = DispatchQueue.init(label: "aaa")
queue.sync {
print(Thread.current)
}
}
所謂當(dāng)前的線程,主要看任務(wù)如何被添加到隊列,任務(wù)(代碼)一定是寫在函數(shù)里的,至少要在函數(shù)里被調(diào)用,例如上面這段代碼,直接寫viewDidLoad(),viewDidLoad運(yùn)行在主線程,如果這時添加一個同步任務(wù),不管是添加到那個隊列里去(主隊列自然不行),代碼運(yùn)行到這,所在的線程就是主線程;
DispatchQueue.global().async {
print("1:\(Thread.current)")
let queue = DispatchQueue.init(label: "q2")
queue.sync {
print("2:\(Thread.current)")
}
}
print("3:\(Thread.current)")
同樣的,上面是一個異步的代碼塊,在其中添加了一個同步任務(wù),因此1和2是同一個線程,3又回到了主線程
let queue = DispatchQueue.init(label: "aaa", qos: .default, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil)
for i in 0 ..< 100{
queue.async {
sleep(3)
print("\(i): even -- async -- \(Thread.current)")
}
}
這段代碼運(yùn)行時,先等待了3秒,然后飛快的輸出了100次,可以看到開啟了很多線程
let queue = DispatchQueue.init(label: "aaa", qos: .default, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil)
for i in 0 ..< 100{
if i % 2 == 0{
queue.async {
print("\(i): even -- async -- \(Thread.current) -- \(Int(Date.init().timeIntervalSince1970))")
}
}else{
queue.sync {
print("\(i): uneven -- sync \(Thread.current) -- \(Int(Date.init().timeIntervalSince1970))")
}
}
}
把上面的代碼改造一下 ,單數(shù)同步,偶數(shù)異步,仍然是飛快的輸出
let queue = DispatchQueue.init(label: "aaa", qos: .default, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil)
for i in 0 ..< 100{
if i % 2 == 0{
queue.async {
print("\(i): even -- async -- \(Thread.current) -- \(Int(Date.init().timeIntervalSince1970))")
}
}else{
queue.sync {
sleep(2)
print("\(i): uneven -- sync \(Thread.current) -- \(Int(Date.init().timeIntervalSince1970))")
}
}
}
再改造一下,給同步的任務(wù)添加一個耗時2秒,再運(yùn)行;
現(xiàn)在發(fā)現(xiàn)兩秒跳一次,說明異步在等待同步,為什么異步?jīng)]有一瞬間執(zhí)行完,讓同步自己去慢慢跑呢:
并發(fā)隊列和串行隊列都是先進(jìn)先出,只不過并發(fā)隊列里的任務(wù)不用等待前面的任務(wù)執(zhí)行完,但是要等前面的任務(wù)開始了,后面的才能開始;
同步的任務(wù)這里在主線程執(zhí)行,主線程就一個,線程也只能做一件事,做完了才能做其他事,前面異步任務(wù)一瞬間完成,是因?yàn)殚_了很多個線程;
這個例子i=3的同步任務(wù)要等待i=1完成之后才能進(jìn)行,而i=3不開始,456...也不能開始,所以后面的任務(wù)雖然是異步的,隊列也是并發(fā)的,但是卻被迫等待前面的任務(wù)完成.
// DispatchQueue.global().async {
DispatchQueue.init(label: "q", qos: .default, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil).async {
let queue = DispatchQueue.init(label: "aaa", qos: .default, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil)
for i in 0 ..< 100{
if i % 2 == 0{
queue.async {
// sleep(3)
print("\(i): even -- async -- \(Thread.current) -- \(Int(Date.init().timeIntervalSince1970))")
}
}else{
queue.sync {
sleep(2)
print("\(i): uneven -- sync \(Thread.current) -- \(Int(Date.init().timeIntervalSince1970))")
}
}
}
}
// }
這個例子在最外面套上DispatchQueue.global().async {} 或者再自定義一個異步也是一樣的,主線程會變成另一個線程,但是仍然要等待.因?yàn)閍sync {}里的線程已經(jīng)確定了,而且就一個.