學(xué)習(xí)多線程的目的:就是將耗時(shí)操作放到后臺(tái)去執(zhí)行灰追。
基本概念
進(jìn)程
? 進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序
? 每個(gè)進(jìn)程之間是獨(dú)立的沸移,每個(gè)進(jìn)程均運(yùn)行在其專用且受保護(hù)的內(nèi)存空間內(nèi)
? 通過(guò) 活動(dòng)監(jiān)視器 可以查看 Mac 系統(tǒng)中所開(kāi)啟的進(jìn)程
線程
? 進(jìn)程要想執(zhí)行任務(wù),必須得有線程娇豫,進(jìn)程至少要有一條線程
? 程序啟動(dòng)會(huì)默認(rèn)開(kāi)啟一條線程,這條線程被稱為主線程或UI 線程
? 線程是進(jìn)程的基本執(zhí)行單元,進(jìn)程的所有任務(wù)都在線程中執(zhí)行
多線程
? 一個(gè)進(jìn)程中可以開(kāi)啟多條線程辉浦,每條線程可以同時(shí)執(zhí)行不同的任務(wù)
○ 進(jìn)程 -> 公司
○ 線程 -> 員工
○ 主線程 -> 老板(第一個(gè)員工)
? 多線程技術(shù)可以提高程序的執(zhí)行效率
多線程原理
? 同一時(shí)間,CPU只能處理一條線程茎辐,只有一條線程在執(zhí)行
? 多線程同時(shí)執(zhí)行宪郊,其實(shí)是CPU快速地在多條線程之間切換
? 如果CPU調(diào)度線程的時(shí)間足夠快,就造成了多線程并發(fā)執(zhí)行的假象
? 如果線程非常多拖陆,會(huì)在多條線程之間來(lái)回切換弛槐,消耗大量的 CPU 資源
○ 每個(gè)線程被調(diào)度的次數(shù)會(huì)降低
○ 線程的執(zhí)行效率會(huì)下降
多線程優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
? 能適當(dāng)提高資源利用率(CPU、內(nèi)存利用率)
? 能適當(dāng)提高程序的執(zhí)行效率
缺點(diǎn)
? 開(kāi)啟線程需要占用一定的內(nèi)存空間依啰,如果開(kāi)啟大量的線程乎串,會(huì)占用大量的內(nèi)存空間,降低程序的性能
? 線程越多速警,CPU在調(diào)度線程上的開(kāi)銷就越大
程序設(shè)計(jì)更加復(fù)雜:比如線程之間的通信叹誉、多線程的數(shù)據(jù)共享
主線程
? 程序啟動(dòng)創(chuàng)建的線程,被稱為主線程或UI 線程
? 主線程的作用
○ 顯示/刷新 UI 界面
○ 處理 UI 事件:點(diǎn)擊闷旧、滾動(dòng)桂对、拖拽等事件
注意:要將耗時(shí)操作放在后臺(tái)線程執(zhí)行,否則會(huì)影響 UI 的流暢度鸠匀,破壞用戶體驗(yàn)
? 所有網(wǎng)絡(luò)訪問(wèn)都是耗時(shí)操作蕉斜!
iOS中多線程的實(shí)現(xiàn)方案
一.pthread
1、簡(jiǎn)介
- pthread 是 POSIX 多線程開(kāi)發(fā)框架缀棍,由于是跨平臺(tái)的 C 語(yǔ)言框架宅此,在蘋(píng)果的頭文件中并沒(méi)有詳細(xì)的注釋。
- 要查閱 pthread 有關(guān)資料爬范,可以訪問(wèn) http://baike.baidu.com父腕。
2、導(dǎo)入頭文件
#import <pthread.h>
3青瀑、pthread演練
// 創(chuàng)建線程璧亮,并且在線程中執(zhí)行 demo 函數(shù)
- (void)pthreadDemo
{
/**
參數(shù):
1>指向線程標(biāo)識(shí)符的指針萧诫,C 語(yǔ)言中類型的結(jié)尾通常
_t/Ref,而且不需要使用 *
-- 在 C 語(yǔ)言中枝嘶,沒(méi)有對(duì)象的概念帘饶,對(duì)象是以結(jié)構(gòu)體的方式來(lái)實(shí)現(xiàn)的。
-- 通常群扶,在C 語(yǔ)言框架中及刻,對(duì)象類型以 _t/Ref 結(jié)尾,而且聲明時(shí)不需要使用 *
2>用來(lái)設(shè)置線程屬性
3> 線程運(yùn)行函數(shù)的起始地址
--- 在C 語(yǔ)言中竞阐,函數(shù)名就是指向函數(shù)在內(nèi)存中的起始地址
--- 類似的一個(gè)概念:數(shù)組名是指向數(shù)組第一個(gè)元素的地址缴饭。
4>運(yùn)行函數(shù)的參數(shù)
在 C 語(yǔ)言中,void *(指向任何地址的指針) 和 OC中的 id(萬(wàn)能指針) 是等價(jià)的
參數(shù)4的格式: void * (*) (void *)
返回值 (*函數(shù)指針) (參數(shù))
返回值:
- 若線程創(chuàng)建成功骆莹,則返回0
- 若線程創(chuàng)建失敗颗搂,則返回出錯(cuò)編號(hào)
*/
pthread_t threadId = NULL;
NSString*str = @"Hello Pthread";
int result = pthread_create(&threadId,NULL, demo, (__bridge void *)(str));
if (result == 0)
{
NSLog(@"創(chuàng)建線程O(píng)K");
}else {
NSLog(@"創(chuàng)建線程失敗%d", result);
}
}
// 后臺(tái)線程調(diào)用函數(shù)
void *demo(void
*params) {
NSString *str = (__bridge
NSString *)(params);
NSLog(@"%@ - %@", [NSThreadcurrentThread], str);
returnNULL;
}
4、小結(jié)
- 在 C語(yǔ)言中幕垦,沒(méi)有對(duì)象的概念峭火,對(duì)象是以結(jié)構(gòu)體的方式來(lái)實(shí)現(xiàn)的。
- 通常智嚷,在 C語(yǔ)言框架中,對(duì)象類型以 _t/Ref 結(jié)尾纺且,而且聲明時(shí)不需要使用 *
- C 語(yǔ)言中的 void * 和 OC 中的 id 是等價(jià)的
- 內(nèi)存管理
- 在 OC 中盏道,如果是 ARC 開(kāi)發(fā),編譯器會(huì)在編譯時(shí)载碌,會(huì)根據(jù)代碼結(jié)構(gòu)猜嘱,自動(dòng)添加retain/release/autorelease
- 但是,ARC 只負(fù)責(zé)管理 OC 部分的內(nèi)存管理嫁艇,而不負(fù)責(zé) C
語(yǔ)言 代碼的內(nèi)存管理 - 因此朗伶,開(kāi)發(fā)過(guò)程中,如果使用的 C 語(yǔ)言框架出現(xiàn) retain/create/copy/new 等字樣的函數(shù)步咪,大多都需要 release论皆,否則會(huì)出現(xiàn)內(nèi)存泄漏
- 在混合開(kāi)發(fā)時(shí),如果在 C 和 OC 之間傳遞數(shù)據(jù)猾漫,需要使用 ____bridge 進(jìn)行橋接点晴,橋接的目的就是為了告訴編譯器如何管理內(nèi)存。__bridge
表示什么特殊處理都不做悯周。 - 橋接的添加可以借助 Xcode的輔助功能添加粒督。
- MRC 中不需要使用橋接。因?yàn)镸RC的內(nèi)存管理需要程序員手動(dòng)管理禽翼。
- 所以,pthread使用起來(lái)比較麻煩,而且是手動(dòng)管理內(nèi)存,幾乎不用
二屠橄、NSThread
1.創(chuàng)建和啟動(dòng)線程
?一個(gè)NSThread對(duì)象就代表一條線程
?創(chuàng)建族跛、啟動(dòng)線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];
//線程一啟動(dòng),就會(huì)在線程thread中執(zhí)行self的run方法
//NSThread 的實(shí)例化方法中的 target 指的是開(kāi)啟線程后锐墙,在線程中執(zhí)行 哪一個(gè)對(duì)象 的 @selector 方法礁哄。
?主線程相關(guān)用法
+(NSThread *)mainThread;
// 獲得主線程
-(BOOL)isMainThread;
// 是否為主線程
+(BOOL)isMainThread;
// 是否為主線程
?獲得當(dāng)前線程
NSThread *current = [NSThread currentThread];
?線程的調(diào)度優(yōu)先級(jí)
+(double)threadPriority;
+(BOOL)setThreadPriority:(double)p;
-(double)threadPriority;
-(BOOL)setThreadPriority:(double)p;
調(diào)度優(yōu)先級(jí)的取值范圍是0.0~1.0,默認(rèn)0.5贮匕,值越大姐仅,優(yōu)先級(jí)越高
?線程的名字
-(void)setName:(NSString *)n;
-(NSString *)name;
2.其他創(chuàng)建線程方式
?創(chuàng)建線程后自動(dòng)啟動(dòng)線程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
?隱式創(chuàng)建并啟動(dòng)線程
[self performSelectorInBackground:@selector(run) withObject:nil];
?上述2種創(chuàng)建線程方式的優(yōu)缺點(diǎn)
- p優(yōu)點(diǎn):簡(jiǎn)單快捷
- p缺點(diǎn):無(wú)法對(duì)線程進(jìn)行更詳細(xì)的設(shè)置
3、線程狀態(tài)
? 新建
○ 實(shí)例化線程對(duì)象
? 就緒
○ 向線程對(duì)象發(fā)送 start 消息刻盐,線程對(duì)象被加入 可調(diào)度線程池 等待CPU調(diào)度
○ detach 方法和 performSelectorInBackground 方法會(huì)直接實(shí)例化一個(gè)線程對(duì)象并加入 可調(diào)度線程池
? 運(yùn)行
○ CPU 負(fù)責(zé)調(diào)度可調(diào)度線程池中線程的執(zhí)行
○ 線程執(zhí)行完成之前掏膏,狀態(tài)可能會(huì)在就緒和運(yùn)行之間來(lái)回切換
○ 就緒和運(yùn)行之間的狀態(tài)變化由 CPU 負(fù)責(zé),程序員不能干預(yù)
? 阻塞
○ 當(dāng)滿足某個(gè)預(yù)定條件時(shí)敦锌,可以使用休眠或鎖阻塞線程執(zhí)行
1.sleepForTimeInterval:休眠指定時(shí)長(zhǎng)
2.sleepUntilDate:休眠到指定日期
3.@synchronized(self):互斥鎖
? 死亡
○ 正常死亡
§ 線程執(zhí)行完畢
○ 非正常死亡
§ 當(dāng)滿足某個(gè)條件后馒疹,在線程內(nèi)部中止執(zhí)行。
當(dāng)滿足某個(gè)條件后乙墙,在主線程中止線程對(duì)象颖变。
控制線程狀態(tài)
?啟動(dòng)線程
-(void)start;
//進(jìn)入就緒狀態(tài) -> 運(yùn)行狀態(tài)。當(dāng)線程任務(wù)執(zhí)行完畢听想,自動(dòng)進(jìn)入死亡狀態(tài)
?阻塞(暫停)線程
+(void)sleepUntilDate:(NSDate *)date;
+(void)sleepForTimeInterval:(NSTimeInterval)ti;
//進(jìn)入阻塞狀態(tài)
?強(qiáng)制停止線程
+(void)exit;
//進(jìn)入死亡狀態(tài)
注意:一旦線程停止(死亡)了腥刹,就不能再次開(kāi)啟任務(wù)
多線程的安全隱患
?資源共享
p1塊資源可能會(huì)被多個(gè)線程共享,也就是多個(gè)線程可能會(huì)訪問(wèn)同一塊資源
p比如多個(gè)線程訪問(wèn)同一個(gè)對(duì)象汉买、同一個(gè)變量衔峰、同一個(gè)文件
?當(dāng)多個(gè)線程訪問(wèn)同一塊資源時(shí),很容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問(wèn)題
安全隱患示例01 – 存錢(qián)取錢(qián)
安全隱患示例02 – 賣票
代碼事例:
- (void)saleTickets {
while (YES) {
[NSThread sleepForTimeInterval:1.0];
@synchronized(self) {
if (self.tickets > 0) {
self.tickets--;
NSLog(@"剩余票數(shù) %d
%@", self.tickets, [NSThread currentThread]);
continue;
}
}
NSLog(@"沒(méi)票了 %@", [NSThread
currentThread]);
break;
}
}
安全隱患解決– 互斥鎖
?互斥鎖使用格式
@synchronized(鎖對(duì)象)
{ //需要鎖定的代碼 }
注意:鎖定1份代碼只用1把鎖,用多把鎖是無(wú)效的
?互斥鎖的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問(wèn)題
缺點(diǎn):需要消耗大量的CPU資源
?互斥鎖的使用前提:多條線程搶奪同一塊資源
?相關(guān)專業(yè)術(shù)語(yǔ):線程同步
線程同步的意思是:多條線程在同一條線上執(zhí)行(按順序地執(zhí)行任務(wù))
互斥鎖蛙粘,就是使用了線程同步技術(shù)
互斥鎖小結(jié)
1.保證鎖內(nèi)的代碼垫卤,同一時(shí)間出牧,只有一條線程能夠執(zhí)行穴肘!
2.互斥鎖的鎖定范圍舔痕,應(yīng)該盡量小,鎖定范圍越大伯复,效率越差盈咳!
3.速記技巧 [[NSUserDefaults standardUserDefaults] synchronize];
互斥鎖參數(shù)
1.能夠加鎖的任意 NSObject 對(duì)象
2.注意:鎖對(duì)象一定要保證所有的線程都能夠訪問(wèn)
3.如果代碼中只有一個(gè)地方需要加鎖边翼,大多都使用 self,這樣可以避免單獨(dú)再創(chuàng)建一個(gè)鎖對(duì)象
原子和非原子屬性
?OC在定義屬性時(shí)有nonatomic和atomic兩種選擇
atomic:原子屬性组底,為setter方法加鎖(默認(rèn)就是atomic)
nonatomic:非原子屬性丈积,不會(huì)為setter方法加鎖
原子屬性內(nèi)部的鎖是自旋鎖筐骇,自旋鎖的執(zhí)行效率比互斥鎖高
自旋鎖&互斥鎖
1、共同點(diǎn)
都能夠保證同一時(shí)間铛纬,只有一條線程執(zhí)行鎖定范圍的代碼
2唬滑、不同點(diǎn)
互斥鎖:如果發(fā)現(xiàn)有其他線程正在執(zhí)行鎖定的代碼,線程會(huì)進(jìn)入休眠狀態(tài)擒悬,等待其他線程執(zhí)行完畢稻艰,打開(kāi)鎖之后,線程會(huì)被喚醒
自旋鎖:如果發(fā)現(xiàn)有其他線程正在執(zhí)行鎖定的代碼尊勿,線程會(huì)以死循環(huán)的方式,一直等待鎖定代碼執(zhí)行完成躯保。
3澎语、結(jié)論
自旋鎖更適合執(zhí)行非常短的代碼
無(wú)論什么鎖,都是要付出代價(jià)
?nonatomic和atomic對(duì)比
atomic:線程安全,需要消耗大量的資源
nonatomic:非線程安全祟滴,適合內(nèi)存小的移動(dòng)設(shè)備
線程安全
- 多個(gè)線程進(jìn)行讀寫(xiě)操作時(shí)歌溉,仍然能夠得到正確結(jié)果,被稱為線程安全
- 要實(shí)現(xiàn)線程安全草慧,必須要用到鎖
- 為了得到更佳的用戶體驗(yàn)匙头,UIKit不是線程安全的
約定:所有更新UI 的操作都必須主線程上執(zhí)行!因此蹂析,主線程又被稱為UI 線程。
iOS開(kāi)發(fā)建議
1.所有屬性都聲明為 nonatomic
2.盡量避免多線程搶奪同一塊資源
3.盡量將加鎖惕稻、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器端處理,減小移動(dòng)客戶端的壓力
線程間通信
?什么叫做線程間通信
在1個(gè)進(jìn)程中公给,線程往往不是孤立存在的蜘渣,多個(gè)線程之間需要經(jīng)常進(jìn)行通信
?線程間通信的體現(xiàn)
- 1個(gè)線程傳遞數(shù)據(jù)給另1個(gè)線程
- 在1個(gè)線程中執(zhí)行完特定任務(wù)后,轉(zhuǎn)到另1個(gè)線程繼續(xù)執(zhí)行任務(wù)
?線程間通信常用方法
-(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
-(void)performSelector:(SEL)aSelectoronThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;