上文我們簡單的敘述了多線程,那么這篇我們就詳細(xì)的說一下!
多線程技術(shù)方案

PThread
導(dǎo)入頭文件
#import <pthread.h>
創(chuàng)建線程
/**
參數(shù)
1. 指向線程標(biāo)識符的指針,C 語言中類型的結(jié)尾通常 _t/Ref,而且不需要使用 *
2. 線程屬性
3. 線程調(diào)用的函數(shù)
void * 返回類型
(*) 函數(shù)指針
void * 參數(shù)類型
4. 運(yùn)行函數(shù)的參數(shù)
返回值
- 若線程創(chuàng)建成功,則返回 0
- 若線程創(chuàng)建失敗,則返回出錯編號
*/
pthread_t threadId =NULL;
char * name ="zhangsan";
NSString * ocName =@"lisi";
int age =19;
// 1> 傳遞 C 語言字符串
//? ? int result = pthread_create(&threadId, NULL, demo, name);
// 2> 傳遞 OC 字符串
//? ? int result = pthread_create(&threadId, NULL, demo, (__bridge void *)(ocName));
// 3> 傳遞基本數(shù)據(jù)類型intresult = pthread_create(&threadId,NULL, demo, &age);
if(result ==0) {
NSLog(@"成功");? ?
}else{
NSLog(@"失敗");? ?
}
線程調(diào)用函數(shù)
/// 線程調(diào)用函數(shù)
void * demo(void* param) {
//? ? NSString *name = (__bridge NSString *)(param);
//? ? NSLog(@"%@ %@", [NSThread currentThread], name);
int * age = param;
NSLog(@"%@ %d", [NSThreadcurrentThread], *age);
return ?NULL;
}
小結(jié)
1. ?在 C 語言中,沒有對象的概念,對象是以結(jié)構(gòu)體的方式來實(shí)現(xiàn)的
2. ?通常,在 C 語言框架中,對象類型以_t/Ref結(jié)尾,而且聲明時不需要使用*
3. ?C 語言中的void *和 OC 中的id是類似的
4. ?內(nèi)存管理
1) ?在 OC 中,如果是ARC開發(fā),編譯器會在編譯時,根據(jù)代碼結(jié)構(gòu),自動添加retain/release/autorelease
2) ?但是,ARC 只負(fù)責(zé)管理 OC 部分的內(nèi)存管理,而不負(fù)責(zé) C 語言 代碼的內(nèi)存管理
3) ?因此,開發(fā)過程中,如果使用的 C 語言框架出現(xiàn)retain/create/copy/new等字樣的函數(shù),大多都需要release,否則會出現(xiàn)內(nèi)存泄漏
4. ?在混合開發(fā)時,如果在 C 和 OC 之間傳遞數(shù)據(jù),需要使用__bridge進(jìn)行橋接,橋接的目的就是為了告訴編譯器如何管理內(nèi)存
1) ?橋接的添加可以借助 Xcode 的輔助功能添加
2) ?MRC 中不需要使用橋接
NSThread
創(chuàng)建線程的三種方式
準(zhǔn)備線程執(zhí)行方法
#pragma mark - 線程執(zhí)行方法
- (void)longOperation:(id)param {
NSLog(@"%@ - %@", [NSThread currentThread], param);
}
alloc/init創(chuàng)建線程
#pragma mark - 創(chuàng)建線程
/// 使用 alloc / init 創(chuàng)建線程
- (void)thread1 {
NSLog(@"before %@", [NSThread currentThread]);
NSThread*thread = [[NSThread alloc] initWithTarget:selfselector:@selector(longOperation:) object:@"hello"];?
? [thread start];
NSLog(@"after %@", [NSThread currentThread]);
}
代碼小結(jié)
1. ?[thread start];執(zhí)行后,會在另外一個線程執(zhí)行l(wèi)ongOperation:方法
2. ?在 OC 中,任何一個方法的代碼都是從上向下順序執(zhí)行的
3. ?同一個方法內(nèi)的代碼,都是在相同線程執(zhí)行的(block除外)
使用類方法創(chuàng)建線程
/// 使用 NSThread 類方法
- (void)thread2 {? ? [NSThreaddetachNewThreadSelector:@selector(longOperation:) toTarget:self withObject:@(__FUNCTION__)];}
注: detachNewThreadSelector類方法不需要啟動,會自動創(chuàng)建線程并執(zhí)行@selector方法
使用 NSObject 分類方法創(chuàng)建線程
/// 使用 NSObject 分類方法
- (void)thread3 {
? ?[self performSelectorInBackground:@selector(longOperation:) ? withObject:@(__FUNCTION__)];
}
代碼小結(jié)
1. ?performSelectorInBackground是NSObject的分類方法
2. ?會自動在后臺線程執(zhí)行@selector方法
3. ?沒有thread字眼,隱式創(chuàng)建并啟動線程
4. ?所有NSObject都可以使用此方法,在其他線程執(zhí)行方法
5. ?Swift中不支持
NSThread 的 Target
NSThread的實(shí)例化方法中的target指的是開啟線程后,在線程中執(zhí)行哪一個對象的@selector方法
準(zhǔn)備對象
@interface Person : NSObject
- (void)run;
@end
@implementation Person
- (void)run {
NSLog(@"跑了 5 分鐘 %@", [NSThread currentThread]);
}
@end
異步執(zhí)行對象方法
// 1. 創(chuàng)建 Person 對象
Person *person = [Person new];
// 2. 在異步執(zhí)行對象方法
// 1> 方式 1
NSThread * t = [[NSThread alloc] initWithTarget:person selector:@selector(run) object:nil];? ?
[t start];
// 2> 方式2
[NSThread detachNewThreadSelector:@selector(run) toTarget:person withObject:nil];
// 3> 方式3
[person performSelectorInBackground:@selector(run) withObject:nil];
代碼小結(jié)
1. ?通過指定不同的target會在后臺線程執(zhí)行該對象的@selector方法
2. ?提示:不要看見target就寫self
3. ?performSelectorInBackground可以讓方便地在后臺線程執(zhí)行任意NSObject對象的方法
線程的狀態(tài)

狀態(tài)說明
新建
1. ?實(shí)例化線程對象
就緒
1. ?向線程對象發(fā)送start消息,線程對象被加入可調(diào)度線程池等待 CPU 調(diào)度
2. ?detach方法和performSelectorInBackground方法會直接實(shí)例化一個線程對象并加入可調(diào)度線程池
運(yùn)行
1. ?CPU負(fù)責(zé)調(diào)度可調(diào)度線程池中線程的執(zhí)行
2. ?線程執(zhí)行完成之前,狀態(tài)可能會在就緒和運(yùn)行之間來回切換
3. ?就緒和運(yùn)行之間的狀態(tài)變化由 CPU 負(fù)責(zé),程序員不能干預(yù)
阻塞
1. ?當(dāng)滿足某個預(yù)定條件時,可以使用休眠或鎖阻塞線程執(zhí)行
2. ?sleepForTimeInterval:休眠指定時長
3. ?sleepUntilDate:休眠到指定日期
4. ?@synchronized(self):互斥鎖
死亡
1. ?正常死亡
2. ?線程執(zhí)行完畢
3. ?非正常死亡
4. ?當(dāng)滿足某個條件后,在線程內(nèi)部中止執(zhí)行
5. ?當(dāng)滿足某個條件后,在主線程中止線程對象
控制線程狀態(tài)的方法
啟動
1. ?[_thread start]
? ? 1) ? 線程進(jìn)入就緒狀態(tài),當(dāng)線程執(zhí)行完畢后自動進(jìn)入死亡狀態(tài)
休眠
1. ?方法執(zhí)行過程中,符合某一條件時,可以利用sleep方法讓線程進(jìn)入阻塞狀態(tài)
? ? 1) ?sleepForTimeInterval從現(xiàn)在起睡多少秒
? ?2) ?sleepUntilDate從現(xiàn)在起睡到指定的日期
死亡
1. ?[NSThread exit]
? ? 1) ?一旦強(qiáng)行終止線程,后續(xù)的所有代碼都不會被執(zhí)行
? ?2) ?注意:在終止線程之前,應(yīng)該注意釋放之前分配的對象!
取消
1. ?[_thread cancel]
1) ?并不會直接取消線程
2) ?只是給線程對象添加isCancelled標(biāo)記
3) ?需要在線程內(nèi)部的關(guān)鍵代碼位置,增加判斷,決定是否取消當(dāng)前線程
代碼演練
定義成員變量
@implementation ViewController{
NSThread*_thread;
}
實(shí)現(xiàn)touch方法
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event {
// 跟蹤線程狀態(tài)
NSLog(@"%@ %zd %zd %zd", _thread, _thread.isFinished, _thread.isCancelled, _thread.isExecuting);
if(_thread ==nil|| _thread.isFinished|| _thread.isCancelled) {
// 1. 實(shí)例化線程
_thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
// 2. 啟動線程,添加到可調(diào)度線程池
[_thread start];
? ? }
}
實(shí)現(xiàn)調(diào)度方法,測試阻塞線程執(zhí)行
- (void)demo {NSLog(@"開始");// 1. 阻塞到指定日期[NSThreadsleepUntilDate:[NSDatedateWithTimeIntervalSinceNow:1]];for(NSIntegeri =0; i <10; i++) {NSLog(@"%zd %@", i, [NSThreadcurrentThread]);// 1> 阻塞指定時長[NSThreadsleepForTimeInterval:1];? ? }NSLog(@"over");}
在線程內(nèi)部直接終止線程
- (void)demo {
NSLog(@"開始");
// 1. 阻塞到指定日期
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
for(NSInteger i =0; i <10; i++) {
NSLog(@"%zd %@", i, [NSThread currentThread]);
// 1> 阻塞指定時長
[NSThread sleepForTimeInterval:1];
// 2> 當(dāng)滿足某一個條件后,直接終止線程,線程終止后,不會執(zhí)行后續(xù)代碼
if(i ==5) {? ? ?
? ? ? [NSThread exit];? ?
? ? }? ?
}
NSLog(@"over");
}
在外部終止線程
修改touch方法
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event {
// 跟蹤線程狀態(tài)
NSLog(@"%@ %zd %zd %zd", _thread, _thread.isFinished, _thread.isCancelled, _thread.isExecuting);
if(_thread ==nil|| _thread.isFinished|| _thread.isCancelled) {
// 1. 實(shí)例化線程_thread = [[NSThreadalloc] initWithTarget:selfselector:@selector(demo) object:nil];
// 2. 啟動線程,添加到可調(diào)度線程池
[_thread start];?
? }else{
// 線程執(zhí)行中,直接終止
[_thread cancel];?
? }
}
運(yùn)行測試,線程并不會被終止
給線程對象發(fā)送cancel消息,只是給線程對象增加的一個標(biāo)記
是否終止需要在線程調(diào)用的代碼內(nèi)部處理
修改demo函數(shù)
- (void)demo {
NSLog(@"開始");
// 1. 阻塞到指定日期
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
if([NSThread currentThread].isCancelled) {
NSLog(@"被取消");
return;? ?
}
for(NSInteger i =0; i <10; i++) {
NSLog(@"%zd %@", i, [NSThread currentThread]);
if([NSThread currentThread].isCancelled) {
NSLog(@"被取消");
return;
? ? ? ? }
// 1> 阻塞指定時長
[NSThread sleepForTimeInterval:1];
// 2> 當(dāng)滿足某一個條件后,直接終止線程,線程終止后,不會執(zhí)行后續(xù)代碼
if(i ==5) {
? ? ? ? ? ? [NSThreadexit];
? ? ? }
? ? }
NSLog(@"over");
}
線程的屬性
常用屬性
1. ?name- 線程名稱
1). 設(shè)置線程名稱可以當(dāng)線程執(zhí)行的方法內(nèi)部出現(xiàn)異常時,記錄異常和當(dāng)前線程
2. ?stackSize- 棧區(qū)大小
1). 默認(rèn)情況下,無論是主線程還是子線程,棧區(qū)大小都是512K
2). 棧區(qū)大小可以設(shè)置[NSThread currentThread].stackSize = 1024 * 1024;
3). 必須是4KB的倍數(shù)
3. ?isMainThread- 是否主線程
4. ?threadPriority- 線程優(yōu)先級
1). 優(yōu)先級,是一個浮點(diǎn)數(shù),取值范圍從0~1.0
2). 1.0表示優(yōu)先級最高
3). 0.0表示優(yōu)先級最低
4). 默認(rèn)優(yōu)先級是0.5
5). 優(yōu)先級高只是保證 CPU 調(diào)度的可能性會高
5. ?qualityOfService- 服務(wù)質(zhì)量(iOS 8.0 推出)
1). NSQualityOfServiceUserInitiated- 用戶需要
2). NSQualityOfServiceUtility- 實(shí)用工具,用戶不需要立即得到結(jié)果
3). NSQualityOfServiceBackground- 后臺
4). NSQualityOfServiceDefault- 默認(rèn),介于用戶需要和實(shí)用工具之間
5). NSQualityOfServiceUserInteractive- 用戶交互,例如繪圖或者處理用戶事件
關(guān)于優(yōu)先級和服務(wù)質(zhì)量
1. ?多線程的目的:是將耗時的操作放在后臺,不阻塞主線程和用戶的交互!
2. ?多線程開發(fā)的原則:簡單
3. ?在開發(fā)時,最好不要修改優(yōu)先級,不要相信用戶交互服務(wù)質(zhì)量
4. ?內(nèi)核調(diào)度算法在決定該運(yùn)行哪個線程時,會把線程的優(yōu)先級作為考量因素
1). 較高優(yōu)先級的線程會比較低優(yōu)先級的線程具有更多的運(yùn)行機(jī)會
2).較高優(yōu)先級不保證你的線程具體執(zhí)行的時間,只是相比較低優(yōu)先級的線程,更有可能被調(diào)度器選擇執(zhí)行而已
代碼
- (void)viewDidLoad {?
? [superview DidLoad];
if([NSThread currentThread].isMainThread){
NSLog(@"主線程 %zd K", [NSThread currentThread].stackSize/1024);?
? }
NSThread * t1 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
// 指定線程名稱,便于調(diào)試
t1.name=@"thread 001";
// 指定線程優(yōu)先級,CPU 會增加改線程的調(diào)度頻率
//? ? t1.threadPriority = 1;
// 指定線程服務(wù)質(zhì)量
t1.qualityOfService=NSQualityOfServiceUserInitiated;
? ? [t1 start];
NSThread * t2 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
? ? t2.name=@"thread 002";
? ? t2.threadPriority=0;
? ? t2.qualityOfService=NSQualityOfServiceBackground;
? ? [t2 start];
}
- (void)demo {
for(NSInteger i =0; i <10; i++) {
NSLog(@"%@ %zd K", [NSThread currentThread], [NSThread currentThread].stackSize/1024);
? ? }
// 模擬線程崩潰
if(![NSThread currentThread].isMainThread) {
int i =10;
int x =0;
NSLog(@"%zd", i / x);
? ? }
}