一蕊梧、進程蹦渣、線程及關(guān)系
一、什么是進程揣炕?
進程是具有一定獨立功能程序關(guān)于某次數(shù)據(jù)集合的一次運行活動秕狰,操作系統(tǒng)分配資源的基本單元也是最小單位稠腊。
進程是指在系統(tǒng)中正在運行的一個應(yīng)用程序,就是一段程序的執(zhí)行過程,我們可以理解為手機上的一個app就是一個進程鸣哀。
每個進程之間是獨立的麻养,每個進程均運行在其專用且受保護的內(nèi)存空間內(nèi),擁有獨立運行所需的全部資源诺舔。
二、什么是線程备畦?
線程是CPU調(diào)度的最小單位低飒。
一個進程要想執(zhí)行任務(wù),必須至少有一條線程.應(yīng)用程序啟動的時候,系統(tǒng)會默認開啟一條線程,也就是主線程
三懂盐、進程和線程的關(guān)系
進程和線程的關(guān)系褥赊,可以簡單的形象的做個比喻:進程=火車,線程=車廂
線程須在進程下才能運行的(單純的車廂無法運行)
一個進程可以包含多個線程(一輛火車可以有多個車廂)
不同進程間的數(shù)據(jù)很難共享(一輛火車上的乘客很難換到另外一輛火車莉恼,比如站點換乘)
同一進程下不同線程間數(shù)據(jù)很易共享(A車廂換到B車廂很容易)
進程要比線程消耗更多的計算機資源(采用多列火車相比多個車廂更耗資源)
進程間不會相互影響拌喉,一個線程掛掉將導(dǎo)致整個進程掛掉(一列火車不會影響到另外一列火車速那,但是如果一列火車中一節(jié)車廂著火了,將影響到所有車廂)
進程可以拓展到多機尿背,線程最多適合多核不同火車(可以開在多個軌道上端仰,同一火車的車廂不能在行進的不同的軌道上)
進程使用的內(nèi)存地址可以上鎖,即一個線程使用某些共享內(nèi)存時田藐,其他線程必須等它結(jié)束荔烧,才能使用這一塊內(nèi)存。(比如火車上的洗手間)-"互斥鎖"
進程使用的內(nèi)存地址可以限定使用量(比如火車上的餐廳汽久,最多只允許多少人進入鹤竭,如果滿了需要在門口等,等有人出來了才能進去)-“信號量”
二景醇、任務(wù)臀稚、隊列及執(zhí)行任務(wù)的方式
一、任務(wù)
- 任務(wù)就是要執(zhí)行的操作三痰,也就是你在線程中執(zhí)行的那段代碼吧寺。在 GCD 中是放在 block 中的。
二酒觅、隊列
一撮执、隊列有兩種:
-
串行隊列:
- 每次只有一個任務(wù)被執(zhí)行。讓任務(wù)一個接著一個地執(zhí)行舷丹。(只開啟一個線程抒钱,一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù))
-
并行隊列:
- 可以讓多個任務(wù)并發(fā)(同時)執(zhí)行颜凯。(可以開啟多個線程谋币,并且同時執(zhí)行任務(wù))
二、執(zhí)行隊列的方式
-
同步執(zhí)行(sync):
- 同步添加任務(wù)到指定的隊列中症概,在添加的任務(wù)執(zhí)行結(jié)束之前蕾额,會一直等待,直到隊列里面的任務(wù)完成之后再繼續(xù)執(zhí)行彼城。
- 只能在當(dāng)前線程中執(zhí)行任務(wù)诅蝶,不具備開啟新線程的能力。
異步執(zhí)行(async):
異步添加任務(wù)到指定的隊列中募壕,它不會造成主線程的等待调炬,可以繼續(xù)執(zhí)行任務(wù).
可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力舱馅。
隊列和任務(wù)的關(guān)系
任務(wù)和隊列的關(guān)系可以打一個形象的比喻缰泡,比如小時后玩的四驅(qū)車,隊列=跑道代嗤,串行隊列=一條跑道棘钞,并行隊列=多條跑道缠借,四驅(qū)車=任務(wù))。
基本規(guī)則就是每條跑道上只能有一輛車在上面跑宜猜。串行隊列由于只有一條跑道泼返,所以每次只能跑一輛車(一個任務(wù)),等這輛車跑完宝恶,別的車(任務(wù))才能跑符隙。并發(fā)隊列由于有多個跑道,所以可以供多輛車(多個任務(wù))一起跑垫毙。
執(zhí)行隊列的方式就相當(dāng)于我們?nèi)绾伟衍嚕ㄈ蝿?wù))放到跑道(隊列)里
-
同步執(zhí)行不開啟新線程:
就相當(dāng)于我們只有主線程一只手霹疫,每次只能把一輛車(任務(wù))放到跑道上跑,等車跑完综芥,把車收了以后丽蝎,才能把下一輛車(任務(wù))放到跑道上跑
所以不管我們是把車(任務(wù))放到單條跑道(串行隊列)還是把車(任務(wù))放到多條跑道(并發(fā)隊列),每次都只能控制一輛車膀藐。并且由于主線程在玩車(執(zhí)行任務(wù))屠阻,也就干不了別的事,所以同步執(zhí)行會造成主線程等待额各。
-
異步執(zhí)行可以開啟新線程:
就相當(dāng)于我們除了有主線程這只手外還邀請了很多一起玩的小伙伴(分線程)国觉,我們每個人都能拿起一輛車(任務(wù))放到對應(yīng)的跑道(隊列)上,然后一起跑虾啦。所以在多條跑道(并發(fā)隊列)上多輛車(多個任務(wù))可以一起跑麻诀。
如果車(任務(wù))很多,跑道只有一個(串行隊列)傲醉,那么還是得排隊玩蝇闭,每次只能跑一輛車(任務(wù))。但是硬毕,由于邀請了小伙伴(開啟了線程)呻引,主線程這只手就可以讓小伙伴(分線程)先玩車,自己去處理別的事情吐咳。所以異步執(zhí)行不會造成主線程等待逻悠。
四、多線程引起的死鎖
死鎖就是隊列引起的循環(huán)等待韭脊。
產(chǎn)生死鎖的情況:
- 一個比較常見的死鎖例子:主隊列同步
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"deallock");
});
}
在主線程中運用主隊列同步蹂风,也就是把任務(wù)放到了主線程的隊列中。
同步對于任務(wù)是立刻執(zhí)行的乾蓬,那么當(dāng)把任務(wù)放進主隊列時,它就會立馬執(zhí)行,只有執(zhí)行完這個任務(wù)慎恒,viewDidLoad才會繼續(xù)向下執(zhí)行任内。
viewDidLoad和任務(wù)都是在主隊列上的撵渡,由于隊列的先進先出原則,任務(wù)又需等待viewDidLoad執(zhí)行完畢后才能繼續(xù)執(zhí)行死嗦,viewDidLoad和這個任務(wù)就形成了相互循環(huán)等待趋距,就造成了死鎖。
想避免這種死鎖越除,可以將同步改成異步dispatch_async,或者將dispatch_get_main_queue換成其他串行或并行隊列节腐,都可以解決。
- 二摘盆、在同串行隊列下異步任務(wù)里調(diào)用同步任務(wù)
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
dispatch_sync(serialQueue, ^{
NSLog(@"deadlock");
});
});
外面的函數(shù)無論是同步還是異步都會造成死鎖翼雀。
這是因為里面的任務(wù)和外面的任務(wù)都在同一個serialQueue隊列內(nèi),又是同步孩擂,這就和上邊主隊列同步的例子一樣造成了死鎖
解決方法也和上邊一樣狼渊,將里面的同步改成異步dispatch_async,或者將serialQueue換成其他串行或并行隊列,都可以解決
二类垦、在不同的串行隊列狈邑,異步block調(diào)用同步不會引起死鎖
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
dispatch_sync(serialQueue2, ^{
NSLog(@"serialQueue2");
});
NSLog(@"serialQueue");
});
serialQueue、serialQueue2是兩個不同隊列蚤认,所以不存在隊列引起的循環(huán)等待米苹。
這樣是不會死鎖的,并且serialQueue和serialQueue2是在同一個線程中的。
打印結(jié)果:
serialQueue2
serialQueue
五砰琢、GCD任務(wù)執(zhí)行順序
- 1蘸嘶、串行隊列先異步后同步
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(serialQueue, ^{
NSLog(@"2");
});
NSLog(@"3");
dispatch_sync(serialQueue, ^{
NSLog(@"4");
});
NSLog(@"5");
代碼打印結(jié)果為:1 3 2 4 5
首先先打印1,
接下來將任務(wù)2其添加至串行隊列上,由于任務(wù)2是異步氯析,不會阻塞線程亏较,繼續(xù)向下執(zhí)行,打印3
然后是任務(wù)4掩缓,將任務(wù)4添加到串行隊列上雪情,因為任務(wù)4和任務(wù)2在同一串行隊列,根據(jù)隊列先進先出原則你辣,任務(wù)4必須等任務(wù)2執(zhí)行后才能執(zhí)行巡通。所以先打印2
又因為任務(wù)4是同步任務(wù),會阻塞線程舍哄,所以再打印4.
最后打印5
- 2宴凉、performSelector
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self performSelector:@selector(test:) withObject:nil afterDelay:0];
});
這里的test方法是不會去執(zhí)行的,原因在于
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument
afterDelay:(NSTimeInterval)delay;
這個方法要創(chuàng)建提交任務(wù)到runloop上的表悬。
gcd底層創(chuàng)建的子線程是默認沒有開啟對應(yīng)runloop的弥锄,所有這個方法就會失效。
解決方法:
將dispatch_get_global_queue改成主隊列,由于主隊列所在的主線程是默認開啟了runloop的籽暇,就會去執(zhí)行温治。
或者將dispatch_async改成同步,因為同步是在當(dāng)前線程執(zhí)行戒悠,那么如果當(dāng)前線程是主線程熬荆,test方法也是會去執(zhí)行的,如果不是就還是不執(zhí)行)绸狐。
在就是開啟子線程的run卤恳,在perfomselector下開發(fā)runloop就行代碼如下:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self performSelector:@selector(test:) withObject:nil afterDelay:0];
[[NSRunLoop currentRunLoop] run];
});
開啟子線程的runloop就會執(zhí)行了。
六寒矿、自旋鎖與互斥鎖
一突琳、自旋鎖
自旋鎖是一種用于保護多線程共享資源的鎖,與一般互斥鎖(mutex)不同之處在于當(dāng)自旋鎖嘗試獲取鎖時以忙等待(busy waiting)的形式不斷地循環(huán)檢查鎖是否可用劫窒。
當(dāng)上一個線程的任務(wù)沒有執(zhí)行完畢的時候(被鎖妆窘瘛),那么下一個線程會一直等待(不會睡眠)主巍,當(dāng)上一個線程的任務(wù)執(zhí)行完畢冠息,下一個線程會立即執(zhí)行。
使用場景:對持有鎖較短的程序
在多CPU的環(huán)境中孕索,對持有鎖較短的程序來說逛艰,使用自旋鎖代替一般的互斥鎖往往能夠提高程序的性能。
自旋鎖會忙等: 所謂忙等搞旭,即在訪問被鎖資源時散怖,調(diào)用者線程不會休眠,而是不停循環(huán)在那里肄渗,直到被鎖資源釋放鎖镇眷。
二、互斥鎖
互斥鎖:當(dāng)上一個線程的任務(wù)沒有執(zhí)行完畢的時候(被鎖佐岬铡)欠动,那么下一個線程會進入睡眠狀態(tài)等待任務(wù)執(zhí)行完畢。
當(dāng)上一個線程的任務(wù)執(zhí)行完畢惑申,下一個線程會自動喚醒然后執(zhí)行任務(wù)具伍。
互斥鎖會休眠: 所謂休眠,即在訪問被鎖資源時圈驼,調(diào)用者線程會休眠人芽,此時cpu可以調(diào)度其他線程工作。直到被鎖資源釋放鎖绩脆。此時會喚醒休眠線程萤厅。
三橄抹、各自優(yōu)缺點:
自旋鎖優(yōu)點:
自旋鎖的優(yōu)點在于,因為自旋鎖不會引起調(diào)用者睡眠祈坠,所以不會進行線程調(diào)度害碾,CPU時間片輪轉(zhuǎn)等耗時操作。
所有如果能在很短的時間內(nèi)獲得鎖赦拘,自旋鎖的效率遠高于互斥鎖。
自旋鎖缺點:
- 自旋鎖一直占用CPU芬沉,他在未獲得鎖的情況下躺同,一直運行--自旋,所以占用著CPU丸逸,如果不能在很短的時 間內(nèi)獲得鎖蹋艺,這無疑會使CPU效率降低。
- 自旋鎖不能實現(xiàn)遞歸調(diào)用黄刚。
自旋鎖:
- atomic
- OSSpinLock
- dispatch_semaphore_t 等
互斥鎖如:
- pthread_mutex
- @ synchronized
- NSLock捎谨、NSConditionLock
- NSCondition
- NSRecursiveLock等
六、NSThread+runloop實現(xiàn)常駐線程
一憔维、為什么去實現(xiàn)常駐線程涛救,常駐線程使用背景?
由于每次開辟子線程都會消耗cpu业扒,在需要頻繁使用子線程的情況下检吆,頻繁開辟子線程會消耗大量的cpu,而且創(chuàng)建線程都是任務(wù)執(zhí)行完成之后也就釋放了程储,不能再次利用蹭沛。
那么如何創(chuàng)建一個線程可以讓它可以再次工作呢?也就是創(chuàng)建一個常駐線程章鲤。
一摊灭、實現(xiàn)常駐線程的步驟
- 首先常駐線程既然是常駐,那么可以用GCD實現(xiàn)一個單例來保存NSThread 代碼如下:
+ (NSThread *)shareThread {
static NSThread *_shareThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_shareThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadTest) object:nil];
[_shareThread setName:@"threadTest"];
[_shareThread start];
});
return _shareThread;
}
這樣創(chuàng)建的thread就不會銷毀了嗎败徊?
[self performSelector:@selector(test) onThread:[ViewController shareThread] withObject:nil waitUntilDone:NO];
- (void)test {
NSLog(@"test:%@", [NSThread currentThread]);
}
執(zhí)行結(jié)果并沒有打印帚呼,說明test方法沒有被調(diào)用。
當(dāng)創(chuàng)建的NSThread沒有開啟runloop的話集嵌,常駐線是不會有效的萝挤。
因為新建的子線程默認沒有開啟runloop
因此需要給這個線程添加了一個runloop,并且加了一個NSMachPort端口監(jiān)聽根欧,防止新建的線程由于沒有活動直接退出怜珍。
- 其次給線程開啟runloop。
需要給這個線程添加了一個runloop凤粗,并且加了一個NSMachPort端口監(jiān)聽酥泛,防止新建的線程由于沒有活動直接退出今豆。。
- (void)threadTest
{
@autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
二柔袁、常駐線程的使用
performSelector是封裝在NSObject的NSThreadPerformAdditions類別里呆躲,只要是NSObject直接調(diào)用
[self performSelector: onThread: withObject: waitUntilDone:]
三、常駐線程退出
只有從runloop中移除我們之前添加的端口捶索,這樣runloop沒有任何事件插掂,所以直接退出。方法如下:
[NSRunLoop currentRunLoop]removePort:<#(nonnull NSPort *)#> forMode:<#(nonnull NSRunLoopMode)#>