主要涉及解決線程間同步及資源共享方面的解決方案。
iOS系統(tǒng)為我們提供的多線程技術方案有哪些肘交?
GCD(使用頻度最高)八堡,NSOperation(圖片異步下載),NSThread(常駐線程)
NSThread
文字簡述:(1)當創(chuàng)建好一個線程并開啟線程(CPU或許在調(diào)度其他的線程旷档,所以也許不會馬上調(diào)度新開啟的這個線程奴饮,因為CPU是來回切換執(zhí)行的),那么就會把這個線程對象放進圖中綠色的可調(diào)度線程池里面進入就緒狀態(tài)等待CPU調(diào)度迈倍,(2)假設過了一會CPU(CPU是切換執(zhí)行的伤靠,會分配給這個線程一個時間片)有時間了切換到這個線程上調(diào)度,那么這個線程就進入運行狀態(tài)啼染,如果這期間CPU在分配的時間片里沒有把這個線程執(zhí)行完畢又停下這個線程的執(zhí)行去調(diào)度其他的線程宴合,那么會記錄此時這個線程執(zhí)行的狀態(tài)再切換到其他線程,此刻這個線程狀態(tài)重新變?yōu)榫途w狀態(tài)(3)如果這個線程調(diào)用sleep方法/等待同步鎖迹鹅,那么這個線程就進入阻塞狀態(tài)卦洽,sleep到時/得到同步鎖,才會重新進入就緒狀態(tài)(4)線程的死亡有自然死亡和非自然死亡斜棚,自然死亡就是線程執(zhí)行完畢阀蒂,非自然死亡就是發(fā)生異常或者程序強制退出了
起了線程名字弟蚀,如若該線程出現(xiàn)異常蚤霞,可以快速定位到問題。
線程執(zhí)行的時間不由我們確定粗梭,每次的執(zhí)行都不確定争便,像剛剛上面這種情況線程是不安全的,不能保證數(shù)據(jù)和預期的結果是一致的断医。
下面是解決方案:用互斥鎖來防止因多線程搶奪資源造成的數(shù)據(jù)安全問題
注意??:如果[多線程]訪問同一個資源,那么必須使用同一把鎖才能鎖住奏纪,在開發(fā)中鉴嗤,盡量不要加鎖,能在服務端做盡量在服務端做序调,如果必須要加鎖醉锅,一定要記住,鎖的范圍不能太大发绢,哪里有[安全隱患]就加在哪里硬耍。
技巧:因為必須使用同一把鎖,開發(fā)中如果需要加鎖边酒,直接使用 self 即可经柴。
線程間通訊
GCD
Test1
Test1文字簡述:
viewDidLoad和Block都是在主線程調(diào)用墩朦,那么兩者都被加入主隊列(主隊列本身就是串行隊列)中坯认,(重點是隊列相互等待,而不是線程問題,不管是不是主隊列 是要是同一個隊列 都會發(fā)生死鎖)由于主隊列先進先出FIFO的特性牛哺,執(zhí)行完viewDidLoad才能執(zhí)行block陋气,可是block是同步在ViewDidLoad方法里,所以就造成了互相等待的死鎖問題引润。
Test2
test2文字簡述:
重點在于是加在了不同的隊列巩趁。block是加在一個自定義隊列,而不是與viewDidLoad同一個隊列淳附,所以先執(zhí)行viewDidLoad议慰,執(zhí)行到block的時候,因為是同步的方式 所以會先把serialQueue上的任務dosomething執(zhí)行完燃观,就會繼續(xù)往下執(zhí)行褒脯,所以是沒有問題的,不存在相互等待的問題
Test3
Test3答案簡述:輸出:1缆毁,2番川,3,4脊框,5颁督,這是對同步并發(fā)隊列的理解
global_queue為全局并發(fā)隊列,里面的任務為并發(fā)執(zhí)行。
按代碼順序輸出1浇雹,(同步方式提交任務到并發(fā)隊列還是串行隊列沉御,它都是在當前線程完成)同步方式提交一個任務到全局并發(fā)隊列中,那么打印2在主線程中執(zhí)行昭灵,同步方式提交一個任務到全局并發(fā)隊列中吠裆,那么打印3在主線程中執(zhí)行,然后打印4烂完,打印5
Test4
文字簡述:
- performSelector:withObject:afterDelay:其實就是在內(nèi)部創(chuàng)建了一個NSTimer试疙,然后會添加到當前線程的Runloop中
- 因為方法里有延遲執(zhí)行,所以內(nèi)部有NSTimer計時抠蚣,timer只有在runloop開啟的狀態(tài)下才能執(zhí)行祝旷,而這個block塊是異步方式,子線程的runloop默認情況下是關閉狀態(tài)嘶窄,所以不會調(diào)用printLog
那么怎樣才可以打印1怀跛,2,3呢柄冲?把子線程的runloop打開不就好了嗎吻谋?是的 在log3上面,performSelector下面這個位置加上
[[NSRunLoop currentRunLoop] run];
那么疑問又來了 是不是在block塊里哪個位置都可以比如log1下面羊初?答案是不行的 下面截出run的一段官方文檔如下:
Discussion
If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it runs the receiver in the `NSDefault<wbr>Run<wbr>Loop<wbr>Mode`by repeatedly invoking [runMode:beforeDate:](apple-reference-documentation://hcGlc34FMW). In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers.
Manually removing all known input sources and timers from the run loop is not a guarantee that the run loop will exit. macOS can install and remove additional input sources as needed to process requests targeted at the receiver’s thread. Those sources could therefore prevent the run loop from exiting.
意思就是如果當前runloop沒有任何事件(source滨溉、timer什湘、observer)的話,那么它會馬上退出晦攒,所以應該寫在selector下面闽撤,這樣就可以保證開啟runloop的時候 有事件timer,那么runloop就不會退出了
Test5 多讀單寫
多讀單寫方案:
dispatch_barrier_async(dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT), ^{
//寫操作
});
為什么要同步讀取呢脯颜?因為獲得數(shù)據(jù)是想可以很快得到的哟旗,實現(xiàn)多讀就是objectforkey可以在多個線程調(diào)用,所以多個線程傳入并發(fā)隊列栋操,也就實現(xiàn)了多讀闸餐。
Test6: dispatch_group_async
dispatch_queue_t concurrent_queue = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
//創(chuàng)建一個隊列組
dispatch_group_t group = dispatch_group_create();
for (int i = 0; i < 3; i ++) {
//異步組分派到并發(fā)隊列當中
//在隊列組group中的concurrent_queue隊列中添加任務
dispatch_group_async(group, concurrent_queue, ^{
//下載
});
}
//會執(zhí)行dispatch_group_notify這個方法,但是里面的block塊只會當隊列中的人物全部執(zhí)行完畢才會執(zhí)行
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//當添加到組中的所有任務執(zhí)行完成之后會調(diào)用該block
});
Test7
1.NSOperation任務執(zhí)行狀態(tài)有:
- isReady. 當前任務是否就緒
- isExcecuting 是否處于正在執(zhí)行中
- isFinish 是否已完成
- isCancel led 是否已取消
2.狀態(tài)控制:
- 如果只重寫了main方法矾芙,底層控制變更任務執(zhí)行完成狀態(tài)舍沙,以及任務退出
- 如果重寫了start方法,自行控制任務狀態(tài)
只重寫main源碼分析:首先看start方法:首先是創(chuàng)建一個自動釋放池剔宪,然后獲取線程的優(yōu)先級拂铡,然后進行一系列狀態(tài)異常判斷,如果沒有異常葱绒,會判斷是否處于正在執(zhí)行中感帅,如果是沒有執(zhí)行,則設置為正在執(zhí)行的狀態(tài)地淀,之后會在判斷當前任務是否被取消失球,如果沒有被取消,就會調(diào)用NSOperation 的 main函數(shù)帮毁,之后調(diào)用finish实苞,之后調(diào)用自動釋放池的release操作。
Test8 NSThead
test:NSThead啟動流程的內(nèi)部實現(xiàn):首先創(chuàng)建一個NSThead之后會調(diào)用start方法烈疚,然后來啟動線程硬梁,在start方法內(nèi)部會創(chuàng)建一個pThead線程,然后會指定pThead中的一個啟動函數(shù)胞得,在啟動函數(shù)中會調(diào)用pThead所定義的main函數(shù), 之后在main函數(shù)中會通過target performSelector來執(zhí)行目標函數(shù)selector,最后調(diào)用exit來結束這個線程
test: 如果要實現(xiàn)一個常駐線程的話屹电,可以在selector方法里維護一個runloop阶剑,實現(xiàn)事件的運行循環(huán),從而達到實現(xiàn)常駐線程的目的
Test8 鎖
iOS當中有哪些鎖危号?
- @synchronized:一般在創(chuàng)建單例對象的時候使用牧愁,從而保證在多線程情況下創(chuàng)建對象是唯一的。
- atomic:修飾屬性的關鍵字外莲;對被修飾對象進行原子操作(不負責使用猪半,就是只對賦值setter保證線程安全 但是對于可變數(shù)組的使用add delete不保證線程安全的)
- OSSpinLock自旋鎖:循環(huán)等待訪問兔朦,不釋放當前資源(比如去房間有人會一直敲門),用于輕量級數(shù)據(jù)訪問磨确,簡單的int值 +1/-1操作
- NSLock解決線程同步問題 來保證各個線程互斥進入自己的臨界區(qū)
- NSRecursiveLock 遞歸鎖
-
dispatch_semaphore_t
簡述:鎖已經(jīng)使用過了沽甥,所以需要等到鎖解開才可以再次訪問加鎖,線程被阻塞住了乏奥,這就造成了死鎖摆舟。
簡述:在這種情況下,我們就可以使用NSRecursiveLock邓了。它可以允許同一線程多次加鎖恨诱,而不會造成死鎖。遞歸鎖會跟蹤它被lock的次數(shù)骗炉。每次成功的lock都必須平衡調(diào)用unlock操作照宝。只有所有達到這種平衡,鎖最后才能被釋放句葵,以供其它線程使用厕鹃。