多線程
如有錯誤歡迎指正,謝謝脱茉。
1.線程與進程
地址空間:進程之間的地址相對獨立,同一個進程中的線程共享本進程的地址空間
資源擁有:進程之間的資源相對獨立,同一個進程中的線程共享本進程的資源禀挫,如內(nèi)存、I/O拓颓、CPU等
2.多線程作用
優(yōu)點
提高程序的執(zhí)行效率
提高資源的利用率———CPU
缺點
開辟線程需要占用一定的內(nèi)存空間
增加了程序復(fù)雜度
開啟了大量線程语婴,會占用大量的內(nèi)存空間,降低了程序性能
3.多線程原理
Cpu在單位時間片里在各個線程之間切換
多線程“同時”進行,多個線程之間切換砰左。
4.線程的生命周期
新建:start
就緒:runnable
運行:running
堵塞:blocked或者sleep匿醒、等待同步鎖、從可調(diào)度線程池移除
死亡:任務(wù)執(zhí)行完成缠导、強制退出
5.線程安全
例子:for循環(huán)里面對數(shù)組進行增刪
三種鎖廉羔,盡量使用nslock
Gcd控制并發(fā)量,信號量? dispatch_semaphore
@synchronize? 遞歸鎖
nslock
6.
自旋鎖? 適合代碼量較小僻造,耗時較少憋他,(忙等,就是里面有人髓削,一直在外面轉(zhuǎn))
互斥鎖? (睡覺竹挡,就是里面有人,他睡覺蔬螟,沒人需要醒來操作)
讀寫鎖? set get此迅,在set方法里面加一個鎖(如@synchronize),讀不會影響寫旧巾,寫會影響讀
7.runloop和線程一一對應(yīng)
線程依賴于runloop
nstimer依賴于runloop
問題
為什么UI要在主線程更新耸序?
Uikit? 設(shè)計機制,比如寫代碼為什么會報警告鲁猩,違反了蘋果的設(shè)計標(biāo)準(zhǔn)坎怪,(異步渲染)
GCD
任務(wù):就是執(zhí)行操作的意思,換句話說就是你在線程中執(zhí)行的那段代碼廓握。在 GCD 中是放在 block 中的搅窿。
執(zhí)行任務(wù)有兩種方式:『同步執(zhí)行』和『異步執(zhí)行』。兩者的主要區(qū)別是:是否等待隊列的任務(wù)執(zhí)行結(jié)束隙券,以及是否具備開啟新線程的能力男应。
串行與并行針對的是隊列,而同步與異步娱仔,針對的則是線程沐飘。
同步執(zhí)行:比如這里的dispatch_sync,這個函數(shù)會把一個block加入到指定的隊列中牲迫,而且會一直等到執(zhí)行完blcok耐朴,這個函數(shù)才返回。因此在block執(zhí)行完之前盹憎,調(diào)用dispatch_sync方法的線程是阻塞的筛峭。
異步執(zhí)行:一般使用dispatch_async,這個函數(shù)也會把一個block加入到指定的隊列中陪每,但是和同步執(zhí)行不同的是影晓,這個函數(shù)把block加入隊列后不等block的執(zhí)行就立刻返回了镰吵。
串行隊列:比如這里的dispatch_get_main_queue。這個隊列中所有任務(wù)俯艰,一定按照先來后到的順序執(zhí)行捡遍。不僅如此,還可以保證在執(zhí)行某個任務(wù)時竹握,在它前面進入隊列的所有任務(wù)肯定執(zhí)行完了画株。對于每一個不同的串行隊列,系統(tǒng)會為這個隊列建立唯一的線程來執(zhí)行代碼啦辐。
并發(fā)隊列:比如使用dispatch_get_global_queue谓传。這個隊列中的任務(wù)也是按照先來后到的順序開始執(zhí)行,注意是開始芹关,但是它們的執(zhí)行結(jié)束時間是不確定的续挟,取決于每個任務(wù)的耗時。對于n個并發(fā)隊列侥衬,GCD不會創(chuàng)建對應(yīng)的n個線程而是進行適當(dāng)?shù)膬?yōu)化
函數(shù)
隊列
死鎖:死鎖是指兩個或兩個以上的進程在執(zhí)行過程中诗祸,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無外力作用轴总,它們都將無法推進下去直颅。此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠在互相等待的進程稱為死鎖進程怀樟。
dispatch_queue_t queue = dispatch_queue_create("com.demo.queue", DISPATCH_QUEUE_SERIAL);//串行隊列
? ? NSLog(@"1"); // 任務(wù)1
? ? dispatch_async(queue, ^{
? ? ? ? NSLog(@"2"); // 任務(wù)2
? ? ? ? dispatch_sync(queue, ^{
? ? ? ? ? ? NSLog(@"3"); // 任務(wù)3
? ? ? ? });
? ? ? ? NSLog(@"4"); // 任務(wù)4
? ? });
? ? NSLog(@"5");
控制臺輸出
1
5
2
分析:
1.執(zhí)行任務(wù)1功偿;
2.遇到異步線程,將【任務(wù)2往堡、同步線程械荷、任務(wù)4】加入串行隊列中。因為是異步線程虑灰,所以在主線程中的任務(wù)5不必等待異步線程中的所有任務(wù)完成吨瞎;
3.因為任務(wù)5不必等待,而開辟線程需要耗時穆咐,所以5先于2執(zhí)行
4.任務(wù)2執(zhí)行完以后关拒,遇到同步線程,這時庸娱,將任務(wù)3加入串行隊列;
5.又因為任務(wù)4比任務(wù)3早加入串行隊列谐算,所以熟尉,任務(wù)3要等待任務(wù)4完成以后,才能執(zhí)行(遵循先進先出原則)洲脂;但是任務(wù)3所在的同步線程需要先執(zhí)行完才能走任務(wù)4斤儿,因為這個同步線程和任務(wù)4都在一個串行隊列中剧包;造成死鎖。
NSLog(@"1"); // 任務(wù)1
? ? dispatch_sync(dispatch_get_main_queue(), ^{
? ? ? ? NSLog(@"2"); // 任務(wù)2
? ? });
? ? NSLog(@"3"); // 任務(wù)3
因為dispatch_sync是同步的往果,本身就會阻塞當(dāng)前線程疆液,此刻阻塞了主線程。而當(dāng)前block又在等待主線程執(zhí)行完畢陕贮,從而形成了主線程等待主線程堕油,自己等自己的情況,形成了死鎖肮之。
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{? ? // 異步執(zhí)行 + 串行隊列
? ? dispatch_sync(queue, ^{? // 同步執(zhí)行 + 當(dāng)前串行隊列
? ? ? ? // 追加任務(wù) 1
? ? ? ? [NSThread sleepForTimeInterval:2];? ? ? ? ? ? ? // 模擬耗時操作
? ? ? ? NSLog(@"1---%@",[NSThread currentThread]);? ? ? // 打印當(dāng)前線程
? ? });
});
執(zhí)行上面的代碼會導(dǎo)致串行隊列中追加的任務(wù)和串行隊列中原有的任務(wù)兩者之間相互等待掉缺,阻塞了『串行隊列』,最終造成了串行隊列所在的線程(子線程)死鎖問題戈擒。
隊列
dispatch_get_main_queue()主隊列
dispatch_get_global_queue()全局并發(fā)隊列? dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>)眶明,第一個參數(shù)標(biāo)識隊列的優(yōu)先級,一般寫0筐高,第二個也寫0
柵欄函數(shù)(只對自定義的并發(fā)隊列有效果搜囱,對全局并發(fā)隊列無效果),為什么對全局并發(fā)隊列無效果柑土?全局并發(fā)隊列是全局的蜀肘,柵欄函數(shù)相當(dāng)于堵塞,這個會導(dǎo)致系統(tǒng)GG
A冰单、B幌缝、C、D任務(wù)有先后順序诫欠,先A涵卵、B,后C荒叼、D無順序轿偎,A、B是請求被廓,獲取到某數(shù)據(jù)才走C坏晦、D
ABCD在同一個異步并發(fā)隊列中
A
B
dispatch_barrier_async? dispatch_barrier_sync ,加a與不加的區(qū)別:不加a會堵塞不在當(dāng)前隊列的任務(wù)嫁乘,1昆婿,2會堵塞在柵欄函數(shù)后。
nslog(@“1”)蜓斧;
C
nslog(@“2”)仓蛆;
D
這樣A、B會無順序執(zhí)行挎春,A看疙、B執(zhí)行完成之后才會走C豆拨、D
柵欄函數(shù)作用
1.順序執(zhí)行
2.線程安全
3.不優(yōu)秀:(1)依賴于一個queue,必須要求堵塞在同一個隊列能庆,如果是不同隊列就沒用了施禾,不利于封裝
/**
?* 柵欄方法 dispatch_barrier_async
?*/
- (void)barrier {
? ? dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
? ? dispatch_async(queue, ^{
? ? ? ? // 追加任務(wù) 1
? ? ? ? [NSThread sleepForTimeInterval:2];? ? ? ? ? ? ? // 模擬耗時操作
? ? ? ? NSLog(@"1---%@",[NSThread currentThread]);? ? ? // 打印當(dāng)前線程
? ? });
? ? dispatch_async(queue, ^{
? ? ? ? // 追加任務(wù) 2
? ? ? ? [NSThread sleepForTimeInterval:2];? ? ? ? ? ? ? // 模擬耗時操作
? ? ? ? NSLog(@"2---%@",[NSThread currentThread]);? ? ? // 打印當(dāng)前線程
? ? });
? ? dispatch_barrier_async(queue, ^{
? ? ? ? // 追加任務(wù) barrier
? ? ? ? [NSThread sleepForTimeInterval:2];? ? ? ? ? ? ? // 模擬耗時操作
? ? ? ? NSLog(@"barrier---%@",[NSThread currentThread]);// 打印當(dāng)前線程
? ? });
? ? dispatch_async(queue, ^{
? ? ? ? // 追加任務(wù) 3
? ? ? ? [NSThread sleepForTimeInterval:2];? ? ? ? ? ? ? // 模擬耗時操作
? ? ? ? NSLog(@"3---%@",[NSThread currentThread]);? ? ? // 打印當(dāng)前線程
? ? });
? ? dispatch_async(queue, ^{
? ? ? ? // 追加任務(wù) 4
? ? ? ? [NSThread sleepForTimeInterval:2];? ? ? ? ? ? ? // 模擬耗時操作
? ? ? ? NSLog(@"4---%@",[NSThread currentThread]);? ? ? // 打印當(dāng)前線程
? ? });
}
在執(zhí)行完柵欄前面的操作之后,才執(zhí)行柵欄操作搁胆,最后再執(zhí)行柵欄后邊的操作弥搞。
dispatch_after延時執(zhí)行方法
dispatch_after 方法并不是在指定時間之后才開始執(zhí)行處理,而是在指定時間之后將任務(wù)追加到主隊列中丰涉。嚴(yán)格來說拓巧,這個時間并不是絕對準(zhǔn)確的,但想要大致延遲執(zhí)行任務(wù)一死,dispatch_after 方法是很有效的
GCD 一次性代碼(只執(zhí)行一次):dispatch_once
創(chuàng)建單例肛度、或者有整個程序運行過程中只執(zhí)行一次的代碼時
staticdispatch_once_t onceToken;
? ? dispatch_once(&onceToken, ^{
? ? ? ? <#code to be executed once#>//只執(zhí)行 1 次的代碼(這里面默認是線程安全的)
? ? });
dispatch_apply快速迭代方法
通常我們會用 for 循環(huán)遍歷,但是 GCD 給我們提供了快速迭代的方法 dispatch_apply投慈。dispatch_apply 按照指定的次數(shù)將指定的任務(wù)追加到指定的隊列中承耿,并等待全部隊列執(zhí)行結(jié)束。
如果是在串行隊列中使用 dispatch_apply伪煤,那么就和 for 循環(huán)一樣加袋,按順序同步執(zhí)行。但是這樣就體現(xiàn)不出快速迭代的意義了抱既。
NSLog(@"apply---start");
? ? dispatch_apply(6, dispatch_get_global_queue(0, 0), ^(size_t index) {
? ? ? ? NSLog(@"%zu---%@",index,[NSThread currentThread]);
? ? });
? ? NSLog(@"apply---end");
輸出
2021-04-20 19:18:01.716987+0800 RecativeCocoaDemo[13193:722316] apply---start
2021-04-20 19:18:01.717123+0800 RecativeCocoaDemo[13193:722316] 0---<NSThread: 0x600002892dc0>{number = 1, name = main}
2021-04-20 19:18:01.717124+0800 RecativeCocoaDemo[13193:722465] 4---<NSThread: 0x600002894e00>{number = 4, name = (null)}
2021-04-20 19:18:01.717124+0800 RecativeCocoaDemo[13193:722464] 3---<NSThread: 0x6000028f84c0>{number = 6, name = (null)}
2021-04-20 19:18:01.717127+0800 RecativeCocoaDemo[13193:722463] 1---<NSThread: 0x6000028e8040>{number = 3, name = (null)}
2021-04-20 19:18:01.717131+0800 RecativeCocoaDemo[13193:722469] 2---<NSThread: 0x60000289db80>{number = 5, name = (null)}
2021-04-20 19:18:01.717142+0800 RecativeCocoaDemo[13193:722466] 5---<NSThread: 0x6000028e1ec0>{number = 7, name = (null)}
2021-04-20 19:18:01.717282+0800 RecativeCocoaDemo[13193:722316] apply---end
因為是在并發(fā)隊列中異步執(zhí)行任務(wù)职烧,所以各個任務(wù)的執(zhí)行時間長短不定,最后結(jié)束順序也不定。但是 apply---end 一定在最后執(zhí)行。這是因為 dispatch_apply 方法會等待全部任務(wù)執(zhí)行完畢每界。
GCD 隊列組:dispatch_group
有時候我們會有這樣的需求:分別異步執(zhí)行2個耗時任務(wù),然后當(dāng)2個耗時任務(wù)都執(zhí)行完畢后再回到主線程執(zhí)行任務(wù)足删。這時候我們可以用到 GCD 的隊列組。
/**
?* 隊列組 dispatch_group_notify
?*/
- (void)groupNotify {
? ? NSLog(@"currentThread---%@",[NSThread currentThread]);? // 打印當(dāng)前線程
? ? NSLog(@"group---begin");
? ? dispatch_group_t group =? dispatch_group_create();
? ? dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
? ? ? ? // 追加任務(wù) 1
? ? ? ? [NSThread sleepForTimeInterval:2];? ? ? ? ? ? ? // 模擬耗時操作
? ? ? ? NSLog(@"1---%@",[NSThread currentThread]);? ? ? // 打印當(dāng)前線程
? ? });
? ? dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
? ? ? ? // 追加任務(wù) 2
? ? ? ? [NSThread sleepForTimeInterval:2];? ? ? ? ? ? ? // 模擬耗時操作
? ? ? ? NSLog(@"2---%@",[NSThread currentThread]);? ? ? // 打印當(dāng)前線程
? ? });
? ? dispatch_group_notify(group, dispatch_get_main_queue(), ^{
? ? ? ? // 等前面的異步任務(wù) 1锁右、任務(wù) 2 都執(zhí)行完畢后失受,回到主線程執(zhí)行下邊任務(wù)
? ? ? ? [NSThread sleepForTimeInterval:2];? ? ? ? ? ? ? // 模擬耗時操作
? ? ? ? NSLog(@"3---%@",[NSThread currentThread]);? ? ? // 打印當(dāng)前線程
? ? ? ? NSLog(@"group---end");
? ? });
}
調(diào)度組
dispatch_group_async
ispatch_group_enter 進組 signal +1
dispatch_group_enter 標(biāo)志著一個任務(wù)追加到 group,執(zhí)行一次咏瑟,相當(dāng)于 group 中未執(zhí)行完畢任務(wù)數(shù) +1
dispatch_group_leave 出租 signal -1
dispatch_group_leave 標(biāo)志著一個任務(wù)離開了 group拂到,執(zhí)行一次,相當(dāng)于 group 中未執(zhí)行完畢任務(wù)數(shù) -1码泞。
signal=0時 調(diào)用dispatch_group_notify兄旬,上面的任務(wù)都做完了就會走這里
/**
?* 隊列組 dispatch_group_enter、dispatch_group_leave
?*/
- (void)groupEnterAndLeave {
? ? NSLog(@"currentThread---%@",[NSThread currentThread]);? // 打印當(dāng)前線程
? ? NSLog(@"group---begin");
? ? dispatch_group_t group = dispatch_group_create();
? ? dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
? ? dispatch_group_enter(group);
? ? dispatch_async(queue, ^{
? ? ? ? // 追加任務(wù) 1
? ? ? ? [NSThread sleepForTimeInterval:2];? ? ? ? ? ? ? // 模擬耗時操作
? ? ? ? NSLog(@"1---%@",[NSThread currentThread]);? ? ? // 打印當(dāng)前線程
? ? ? ? dispatch_group_leave(group);
? ? });
? ? dispatch_group_enter(group);
? ? dispatch_async(queue, ^{
? ? ? ? // 追加任務(wù) 2
? ? ? ? [NSThread sleepForTimeInterval:2];? ? ? ? ? ? ? // 模擬耗時操作
? ? ? ? NSLog(@"2---%@",[NSThread currentThread]);? ? ? // 打印當(dāng)前線程
? ? ? ? dispatch_group_leave(group);
? ? });
? ? dispatch_group_notify(group, dispatch_get_main_queue(), ^{
? ? ? ? // 等前面的異步操作都執(zhí)行完畢后浦夷,回到主線程.
? ? ? ? [NSThread sleepForTimeInterval:2];? ? ? ? ? ? ? // 模擬耗時操作
? ? ? ? NSLog(@"3---%@",[NSThread currentThread]);? ? ? // 打印當(dāng)前線程
? ? ? ? NSLog(@"group---end");
? ? });
}
dispatch_group_wait
暫停當(dāng)前線程(阻塞當(dāng)前線程)辖试,等待指定的 group 中的任務(wù)執(zhí)行完成后,才會往下繼續(xù)執(zhí)行劈狐。
/**
?* 隊列組 dispatch_group_wait
?*/
- (void)groupWait {
? ? NSLog(@"currentThread---%@",[NSThread currentThread]);? // 打印當(dāng)前線程
? ? NSLog(@"group---begin");
? ? dispatch_group_t group =? dispatch_group_create();
? ? dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
? ? ? ? // 追加任務(wù) 1
? ? ? ? [NSThread sleepForTimeInterval:2];? ? ? ? ? ? ? // 模擬耗時操作
? ? ? ? NSLog(@"1---%@",[NSThread currentThread]);? ? ? // 打印當(dāng)前線程
? ? });
? ? dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
? ? ? ? // 追加任務(wù) 2
? ? ? ? [NSThread sleepForTimeInterval:2];? ? ? ? ? ? ? // 模擬耗時操作
? ? ? ? NSLog(@"2---%@",[NSThread currentThread]);? ? ? // 打印當(dāng)前線程
? ? });
? ? // 等待上面的任務(wù)全部完成后罐孝,會往下繼續(xù)執(zhí)行(會阻塞當(dāng)前線程)
? ? dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
? ? //DISPATCH_TIME_NOW:超時時間為0,表示忽略信號量肥缔,直接運行
? ? //** DISPATCH_TIME_FOREVER**:超時時間為永遠莲兢,表示會一直等待信號量為正數(shù),才會繼續(xù)運行
? ? NSLog(@"group---end");
}
信號量
GCD控制并發(fā)數(shù)
當(dāng)鎖续膳,一次控制一個
dispatch_semaphore_t
dispatch_source_t的應(yīng)用
響應(yīng)式
timer
NSOperation 抽象類改艇,依賴子類實現(xiàn)
特性:靈活、自如
1:創(chuàng)建操作 直接start坟岔,會在主線程執(zhí)行谒兄。(走2,3社付,還走start承疲,會崩潰)
2:創(chuàng)建隊列
3:操作加入隊列
NSInvocationOperation
NSBlockOperation? //功能在一起,代碼可讀性更強
應(yīng)用:
GCD:系統(tǒng)自己管理生命周期鸥咖,
cancel燕鸽,可以取消(已經(jīng)添加到隊列的任務(wù)取消掉)
隊列設(shè)置優(yōu)先級
控制并發(fā)數(shù)? NSOperationQueue的maxConcurrentOperationCount
依賴 addDependency
隊列的掛起、繼續(xù)啼辣、取消(掛起后為什么還會有任務(wù)執(zhí)行啊研?已經(jīng)被調(diào)度的任務(wù)無法被掛起,相當(dāng)于已經(jīng)在執(zhí)行了)
Sdwebimage(模擬網(wǎng)絡(luò)延遲:開發(fā)模式鸥拧、開代理党远、pref軟件)
先從內(nèi)存取,內(nèi)存有結(jié)束住涉,內(nèi)存無從沙盒取麸锉,沙盒也沒有走下載
沙盒取過之后存到內(nèi)存,下次取直接從內(nèi)存取舆声,沒有走下載
內(nèi)存和沙盒都沒有花沉,子線程下載,下載完畢內(nèi)存緩存和沙盒緩存媳握,從內(nèi)存做存取比沙盒快
重復(fù)下載的問題
具體操作
UIimageview分類:任務(wù)
1.判斷url非空碱屁,url重復(fù)性,保存
manager蛾找,下發(fā)下載任務(wù)
接受內(nèi)存警告的通知(盡量不走系統(tǒng)的警告)
添加的時候清理圖片娩脾,超過多大(比如100M)就清理舊的圖片
加一個最大圖片緩存大小,比如100M打毛,超過100M我就給清理一下柿赊,清理到60M(按圖片時間更新時間俩功,沒有用過的圖片,用的頻率小的)碰声,
下載圖片:
nsoperation + 隊列
重寫start方法