一荞彼、什么是多線程
-
多線程
是指從軟件或者硬件上實現(xiàn)多個線程并發(fā)執(zhí)行
的技術(shù)恤左。 - 由于一條線程
同一時間
只能處理一個任務(wù)
,所以線程里的任務(wù)必須按順序
執(zhí)行遂鹊。 - 如果遇到耗時操作(網(wǎng)絡(luò)通信、耗時計算蔗包、音樂播放等)秉扑,等上一個操作完成再執(zhí)行下一個任務(wù),在此段時間內(nèi)调限,用戶得不到任何響應(yīng)舟陆,這個體驗無疑是極糟的。
- 因此耻矮,在iOS編程中秦躯,通常
將耗時操作
單獨放在一個線程中
,而把用戶交互
的操作放在主線程中
淘钟。
二宦赠、進(jìn)程與線程
- 進(jìn)程:
進(jìn)程是指系統(tǒng)中正在運行的應(yīng)用程序陪毡。這個'運行中的程序'就是一個進(jìn)程米母。
每個進(jìn)程都擁有著自己的地址空間勾扭。
進(jìn)程有3個主要特征:
獨立性:
進(jìn)程是一個能夠獨立運行的基本單位,它既擁有自己獨立的資源铁瞒,又擁有著自己私有的地址空間妙色。
在沒有經(jīng)過進(jìn)程本身允許的情況下,一個用戶的進(jìn)程是不可以直接訪問其他進(jìn)程的地址空間的慧耍。
動態(tài)性:
進(jìn)程的實質(zhì)是程序在系統(tǒng)中的一次執(zhí)行過程身辨。
程序只是一個靜態(tài)的指令集合,而進(jìn)程是一個正在系統(tǒng)中活動的指令集合芍碧。
在進(jìn)程中加入了時間的概念煌珊,它就具有了自己的生命周期和各自不同的狀態(tài),進(jìn)程是動態(tài)消亡的泌豆。
并發(fā)性:
多個進(jìn)程可以在單個處理器中同時進(jìn)行定庵,而不會相互影響。
- 線程:
多線程擴展了多進(jìn)程的概念踪危,使得同一進(jìn)程可以同時并發(fā)處理多個任務(wù)蔬浙。
一個程序的運行至少需要一個線程,一個進(jìn)程想要執(zhí)行任務(wù)贞远,也必須要有至少一個線程畴博,而這個線程就被稱作主線程。
通常來說蓝仲,只有一個主線程俱病。
當(dāng)進(jìn)程被初始化后,主線程就被創(chuàng)建了袱结,主線程是其他線程最終的父線程亮隙,所有界面的顯示操作必須在主線程進(jìn)行。
三擎勘、多線程的優(yōu)勢
進(jìn)程間不能共享內(nèi)存咱揍,但線程之間的共享內(nèi)存是很容易的。
當(dāng)硬件處理器的數(shù)量有所增加時,程序運行的速度更快递递,無需做其他調(diào)整盏求。
充分發(fā)揮了多核處理器的優(yōu)勢,將不同的任務(wù)分配給不同的處理器硼砰,真正進(jìn)入“并行運算”的狀態(tài)。
將耗時欣硼、并發(fā)需求高的任務(wù)分配到其他線程執(zhí)行题翰,而主線程則負(fù)責(zé)統(tǒng)一更新界面,這樣使得應(yīng)用程序更加流暢,用戶體驗更好豹障。
四冯事、多線程的劣勢
- 開啟線程需要占用一定的內(nèi)存空間
[默認(rèn)情況下,主線程最大占用1M的棧區(qū)空間血公、子線程最大占用512K的棧區(qū)空間]
昵仅,如果要開啟大量的線程,勢必會占用大量的內(nèi)存空間累魔,從而降低程序的性能摔笤。 - 開啟的線程越多,CPU在調(diào)度線程上的開銷就越大垦写,一般
最好不要同時開啟超過5個線程
吕世。 - 程序的設(shè)計會變得更加復(fù)雜,如線程之間的通信梯投、多線程間的數(shù)據(jù)共享等命辖。
五、線程的串行和并行
- 串行:
如果在一個進(jìn)程中只有一個線程晚伙,而這個進(jìn)程要執(zhí)行多個任務(wù)吮龄,
那么這個線程只能一個一個的按照順序執(zhí)行這些任務(wù),也就是說咆疗,
在同一時間內(nèi)漓帚,一個線程只能執(zhí)行一個任務(wù),這樣的線程執(zhí)行方式稱為線程的串行午磁。
- 并行:
如果一個進(jìn)程中包含的線程不止一條尝抖,
每條線程之間可以并行執(zhí)行不同的任務(wù),這叫做線程的并行迅皇,也叫多線程昧辽。
- 補充:
假如一個進(jìn)程有3個線程,每個線程執(zhí)行1個任務(wù)登颓,3個下載任務(wù)沒有先后順序搅荞。可以同時執(zhí)行框咙。
同一時間咕痛,CPU只能處理一個線程,也就是只有一個線程在工作喇嘱。
由于CPU快速的在多個線程之間調(diào)度茉贡,人眼無法察覺到,就造成了多線程并發(fā)執(zhí)行的假象者铜。
六腔丧、線程的狀態(tài)
當(dāng)線程被創(chuàng)建并啟動之后放椰,既不是一啟動就進(jìn)入執(zhí)行狀態(tài),也不是一直處于執(zhí)行狀態(tài)愉粤,即便程序啟動運行之后砾医,它也不可能一直占用CPU獨自運行。
由于CPU需要在多個線程之間進(jìn)行切換科汗,造成了線程的狀態(tài)會在多次運行藻烤、就緒之間進(jìn)行切換绷雏。
線程的狀態(tài)主要有5個:
1. 新建New
當(dāng)程序新建一個線程之后头滔,該線程就處于新建狀態(tài)。
和其他對象一樣涎显,只是由系統(tǒng)分配了內(nèi)存坤检,并初始化了內(nèi)部成員變量的值。
此時的線程沒有任何動態(tài)特征
2. 就緒Runable
當(dāng)線程被start之后期吓,該線程就處于就緒狀態(tài)早歇。
系統(tǒng)會為其創(chuàng)建 方法調(diào)用的棧和 程序計數(shù)器。
3. 運行Running
當(dāng)CPU調(diào)度當(dāng)前線程的時候讨勤,將其他線程掛起箭跳,當(dāng)前線程變?yōu)檫\行狀態(tài)。
當(dāng)CPU調(diào)度其他線程時潭千,當(dāng)前線程回到就緒狀態(tài)谱姓。
測試線程是否在運行,調(diào)用isExecuting方法刨晴,若返回YES屉来,則處于運行狀態(tài)。
4. 終止Exit
* 線程執(zhí)行方法執(zhí)行完成狈癞,線程正常結(jié)束
* 線程執(zhí)行的過程出現(xiàn)異常茄靠,線程崩潰結(jié)束
* 直接調(diào)用NSThread類的exit方法,終止當(dāng)前正在運行的線程
* 測試線程是否結(jié)束蝶桶,調(diào)用isFinished方法判斷慨绳,若返回YES,則已終止真竖。
5. 阻塞Blocked
如果當(dāng)前正在執(zhí)行的線程需要暫停一段時間脐雪,并進(jìn)入阻塞狀態(tài),通過NSThread類的兩個方法:
//讓當(dāng)前執(zhí)行的線程暫停到date參數(shù)代表的時間疼邀,并且進(jìn)入阻塞狀態(tài)
+ (void)sleepUntilDate:(NSDate *)date;
//讓正在執(zhí)行的線程暫停ti秒喂江,并進(jìn)入阻塞狀態(tài)
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
七、線程間的安全隱患
進(jìn)程中的一塊資源可能會被多個線程共享旁振,也就是多個線程可能訪問同一塊資源获询,這里的資源包括對象
涨岁、變量
、文件
等吉嚣。
當(dāng)多個線程同時訪問同一塊資源時梢薪,會造成資源搶奪
,很容易引發(fā)數(shù)據(jù)錯亂
和數(shù)據(jù)安全
的問題尝哆。
為了解決這個問題秉撇,實現(xiàn)數(shù)據(jù)的安全訪問,可以使用線程間的加鎖
秋泄。
- 數(shù)據(jù)混亂示例:
/** 售票處理 */
- (void)saleTickets{
while (true) {
//模擬延時
[NSThread sleepForTimeInterval:1.0];
//判斷是否還有票
if (self.leftTickets > 0) {
self.leftTickets--;
NSLog(@"%@賣了一張票琐馆,還剩下%lu張票",[NSThread currentThread].name,self.leftTickets);
} else{
NSLog(@"票已售完");
return;
}
}
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//設(shè)定總票數(shù)
self.leftTickets = 100;
//創(chuàng)建3個線程,啟動后執(zhí)行saleTickets方法賣票
NSThread *t1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTickets) object:nil];
t1.name = @"1號窗口";
[t1 start];
NSThread *t2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTickets) object:nil];
t2.name = @"2號窗口";
[t2 start];
NSThread *t3 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTickets) object:nil];
t3.name = @"3號窗口";
[t3 start];
}
可以看到恒序,由于3個線程的并發(fā)操作
瘦麸,同一時間搶奪一個資源
leftTickets,造成了剩余票數(shù)統(tǒng)計的混亂歧胁。
- 加鎖示例:
@synchronized (obj)
{
//插入被修飾的代碼塊
}
- 使用同步鎖的時候滋饲,要盡量讓
同步代碼塊包圍的代碼范圍最小
,而且要鎖定共享資源的全部讀寫部分
的代碼喊巍。
obj
就是加鎖對象
屠缭,添加了鎖對象后,鎖對象實現(xiàn)了對多線程的監(jiān)管崭参,保證同一時刻只有一個線程執(zhí)行
呵曹,當(dāng)同步代碼塊執(zhí)行完成后
,鎖定對象就會釋放同步監(jiān)視器的鎖定阵翎。
/** 售票處理 */
- (void)saleTickets{
while (true) {
//模擬延時
[NSThread sleepForTimeInterval:1.0];
//判斷是否還有票
@synchronized(self){
if (self.leftTickets > 0) {
self.leftTickets--;
NSLog(@"%@賣了一張票逢并,還剩下%lu張票",[NSThread currentThread].name,self.leftTickets);
} else{
NSLog(@"票已售完");
return;
}
}
}
}
線程添加同步鎖后,實現(xiàn)了線程的同步運行郭卫,也就是說砍聊,使多線程按順序執(zhí)行任務(wù)。需要注意的是贰军,同步鎖會消耗大量的CPU資源玻蝌。