多線程基本概念
單核CPU,同一時(shí)間cpu只能處理1個(gè)線程,只有1個(gè)線程在執(zhí)行 。多線程同時(shí)執(zhí)行:是CPU快速的在多個(gè)線程之間的切換。如果線程數(shù)非常多,線程切換會(huì)消耗大量的cpu資源。相同時(shí)間內(nèi),每個(gè)線程被調(diào)度的次數(shù)會(huì)降低,線程的執(zhí)行效率降低 。
優(yōu)點(diǎn):
能適當(dāng)提高程序的執(zhí)行效率
能適當(dāng)提高資源的利用率(CPU&內(nèi)存)
線程上得任務(wù)執(zhí)行完后自動(dòng)銷毀
缺點(diǎn):
開啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下,每一個(gè)線程都占512KB)
如果開啟大量的線程,會(huì)占用大量的內(nèi)存空間,降低程序的性能
線程越多,cpu在調(diào)用線程上的開銷就越大
程序設(shè)計(jì)更加復(fù)雜,比如線程間的通信、多線程的數(shù)據(jù)共享
主線程:
一個(gè)程序運(yùn)行后,默認(rèn)會(huì)開啟1個(gè)線程,稱為“主線程”或“UI線程”
主線程一般用來 刷新UI界面 ,處理UI事件(比如:點(diǎn)擊、滾動(dòng)、拖拽等事件)
主線程使用注意 ?別將耗時(shí)的操作放到主線程中?耗時(shí)操作會(huì)卡住主線程,嚴(yán)重影響UI的流暢度,給用戶一種卡的壞體驗(yàn)
iOS中目前有四種實(shí)現(xiàn)多線程的方案:
1.pthread
2.NSThread
3.GCD
4.NSOperation
pthread
一套在多操作系統(tǒng)上通用的多線程API,跨平臺、可移植性強(qiáng),基于C語言。線程的生命周期需要程序員自己管理,基本不怎么使用。
簡單使用:
創(chuàng)建一個(gè)子線程并在子線程中執(zhí)行一個(gè)函數(shù)。
#import <pthread.h>
#pragma mark --pthread
- (void)pthreadDemo1{
pthread_t PID;
//參數(shù)1:pthread_t 線程的標(biāo)示
//參數(shù)2:pthread_attr_t 線程的屬性
//參數(shù)3:void* (*) (void *) 函數(shù)簽名, void *大約可以理解為oc中的id
// 返回值 函數(shù)名 參數(shù)
//參數(shù)4:給函數(shù)(參數(shù)3)的參數(shù)
//返回值:0 成功,非0 失敗
int result = pthread_create(&PID, NULL, task, NULL);
if (result == 0) {
NSLog(@"ok");
}else{
NSLog(@"fail");
}
}
void * task(void *param){
NSLog(@"task is running %@",[NSThread currentThread]);
return NULL;
}
- (IBAction)clicked:(id)sender {
NSLog(@"main thread:%@",[NSThread currentThread]);
[self pthreadDemo1];
}

- (void)pthreadDemo2{
pthread_t PID;
NSString *str = @"str";
// char * str2 = "str2";
// int result = pthread_create(&PID, NULL, task, str2);
int result = pthread_create(&PID, NULL, task, (__bridge void *)(str));
if (result == 0) {
NSLog(@"ok");
}else{
NSLog(@"fail");
}
}
void * task(void *param){
// printf("%s\n",param);
NSString *str = (__bridge NSString *)(param);
NSLog(@"正在運(yùn)行的線程: %@, ->%@",[NSThread currentThread],param);
NSLog(@"正在運(yùn)行的線程:%@, ->%@",[NSThread currentThread],str);
return NULL;
}
注意:ARC默認(rèn)下對OC對象進(jìn)行內(nèi)存管理,不對C變量管理,橋接的作用是讓C變量在合適的時(shí)候進(jìn)行釋放。在ARC中使用到和C語言對應(yīng)的數(shù)據(jù)類型,就應(yīng)該使用__bridge進(jìn)行橋接,在MRC中則不需要。

使用pthread需要自己管理線程的生命周期,上面的代碼中創(chuàng)建了線程但是沒有銷毀。
NSThread
面向?qū)ο?,可以直接操作線程對象,基于OC語言。線程的生命周期仍然需要程序員自己進(jìn)行管理,使用頻率不高??梢酝ㄟ^[NSThread currentThread]獲取當(dāng)前線程,以此獲取線程的各種屬性,便于調(diào)試。
創(chuàng)建和啟動(dòng)線程
- 需要手動(dòng)啟動(dòng)
//參數(shù)1:對象
//參數(shù)2:方法
//參數(shù)3:參數(shù)2方法需要的參數(shù)
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(task2) object:nil];
[thread start];//線程啟動(dòng)后在thread中執(zhí)行task2方法
- 自動(dòng)啟動(dòng)
[NSThread detachNewThreadSelector:@selector(task2) toTarget:self withObject:nil];
或者:
//隱式創(chuàng)建并自動(dòng)啟動(dòng)線程
[self performSelectorInBackground:@selector(task2) withObject:nil];
后面這兩種創(chuàng)建線程并自動(dòng)啟動(dòng)線程的方法有一個(gè)缺點(diǎn):沒有辦法對線程的一些屬性進(jìn)行設(shè)置。
線程的狀態(tài)
-
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(task3) object:nil];線程新建狀態(tài)。在內(nèi)存中創(chuàng)建出一個(gè)線程對象。
-
[thread start];線程就緒狀態(tài)。線程對象被放入可調(diào)度線程池,池中還有別的其他線程對象。等待CPU調(diào)度。
CPU調(diào)度當(dāng)前線程,線程進(jìn)入運(yùn)行狀態(tài)。當(dāng)CPU調(diào)度其他線程,線程回到就緒狀態(tài)。
-
[NSThread sleepForTimeInterval:1];線程阻塞狀態(tài)。當(dāng)調(diào)用了sleep方法,或者在等待同步鎖。線程對象移出可調(diào)度線程池
當(dāng)sleep時(shí)間到時(shí)或得到了同步鎖,回到就緒狀態(tài)。
-
線程死亡。線程任務(wù)執(zhí)行完畢,自然死亡;強(qiáng)制退出,手動(dòng)殺死。線程一旦死亡了,就不能再次開啟任務(wù)。
- (void)stateDemo{
//新建狀態(tài)
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(task3) object:nil];
//就緒狀態(tài)
[thread start];
}
- (void)task3{
//運(yùn)行
NSLog(@"正在運(yùn)行的線程:%@",[NSThread currentThread]);
//阻塞
NSLog(@"線程即將進(jìn)入阻塞狀態(tài)");
[NSThread sleepForTimeInterval:1];
// [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];//線程休眠時(shí)間
//從阻塞->就緒->運(yùn)行。被cpu調(diào)用,進(jìn)入運(yùn)行狀態(tài)
NSLog(@"線程喚醒");
//線程死亡??梢宰匀凰劳鲆部梢员皇謩?dòng)殺死,當(dāng)線程任務(wù)執(zhí)行完畢,自動(dòng)進(jìn)入死亡狀態(tài)
//手動(dòng)殺死
[NSThread exit];
NSLog(@"dead");//這句話將不會(huì)被打印出來
//一旦線程死亡,就不能再次開啟任務(wù)
}

常用屬性、方法
更多的屬性、方法請參閱文檔
名字屬性。
@property (nullable, copy) NSString *name;
設(shè)置線程名:
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(task4) object:nil];
thread.name = @"thread";
獲得當(dāng)前線程
+ (NSThread *)currentThread;
獲得主線程
+ (NSThread *)mainThread;
是否為主線程
- (BOOL)isMainThread;
線程優(yōu)先級
+ (double)threadPriority;
設(shè)置優(yōu)先級:
- (void)attrDemo2{
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(task4) object:nil];
thread.name = @"thread";
thread.threadPriority = 0;
[thread start];
NSThread *thread2 = [[NSThread alloc]initWithTarget:self selector:@selector(task4) object:nil];
thread2.name = @"thread2";
[thread2 start];
}
- (void)task4{
for (int i = 0; i<20; i++) {
NSLog(@"當(dāng)前線程:%@,%d",[NSThread currentThread],i);
}
}
線程優(yōu)先級取值范圍0.0-1.0,默認(rèn)為0.5,值越大優(yōu)先級越高。設(shè)置優(yōu)先級并不意味著優(yōu)先級高的線程要比優(yōu)先級低的線程先運(yùn)行(運(yùn)行反映的結(jié)果就是,打印結(jié)果里面顯示的第一條是哪個(gè)thread),只是更可能被CPU執(zhí)行到。先執(zhí)行哪個(gè)線程是由cpu調(diào)度決定的。

線程間通信
常用方法:NSThread類
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
GCD
旨在替代NSThread等的線程技術(shù),充分利用多核,基于C語言。線程的生命周期是自動(dòng)管理的,不需要程序員手動(dòng)管理線程的創(chuàng)建/銷毀/復(fù)用過程。經(jīng)常使用。
任務(wù)、隊(duì)列
任務(wù)和隊(duì)列是GCD中的兩個(gè)核心概念。
任務(wù)就是要執(zhí)行什么操作,在GCD中通過block來指定要執(zhí)行的代碼。隊(duì)列用來存放任務(wù)。
GCD使用的兩個(gè)步驟:
1.定制任務(wù):確定想做的事情
2.將任務(wù)添加到隊(duì)列中,指定運(yùn)行方式。
GCD會(huì)自動(dòng)將隊(duì)列中的任務(wù)取出,放到對應(yīng)的線程中執(zhí)行。任務(wù)的取出遵循隊(duì)列的FIFO原則,先進(jìn)先出。
//創(chuàng)建任務(wù)
//dispatch_block_t的定義typedef void (^dispatch_block_t)(void);任務(wù)實(shí)際上是一個(gè)Block
dispatch_block_t task = ^{
NSLog(@"task %@",[NSThread currentThread]);
};
//獲取隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//把任務(wù)放到隊(duì)列中
//參數(shù)1:隊(duì)列 參數(shù)2:任務(wù)
dispatch_async(queue, task);
//簡化
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"task %@",[NSThread currentThread]);
});
不需要管理線程的生命周期;線程能夠復(fù)用
for (int i = 0; i < 20; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"task %@ %d",[NSThread currentThread] , i);
});
}

- 隊(duì)列
隊(duì)列的類型:并發(fā)隊(duì)列、串行隊(duì)列。
并發(fā)隊(duì)列:可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行(自動(dòng)開啟多個(gè)線程“同時(shí)”執(zhí)行任務(wù))。并發(fā)功能只有在異步時(shí)才有效。
串行隊(duì)列:無論是同步異步,任務(wù)都是一個(gè)接一個(gè)地執(zhí)行。
無論是串行隊(duì)列還是并發(fā)隊(duì)列,隊(duì)列里面的任務(wù)取出都遵循FIFO原則。并發(fā)隊(duì)列取出任務(wù)就分發(fā)到可用的線程里,取出的動(dòng)作很快,就相當(dāng)于是所有任務(wù)都是一起執(zhí)行的。
全局并發(fā)隊(duì)列:供整個(gè)應(yīng)用使用,GCD有函數(shù)可以獲得,不需要手動(dòng)創(chuàng)建
主隊(duì)列:它是特殊的串行隊(duì)列,又叫全局串行隊(duì)列,代表著主線程。
隊(duì)列的創(chuàng)建:
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);第一個(gè)參數(shù)是唯一標(biāo)識符,也可以當(dāng)做是隊(duì)列的名稱,用于debug。第二個(gè)參數(shù)是隊(duì)列的屬性,表示創(chuàng)建的是并行隊(duì)列還是串行隊(duì)列,DISPATCH_QUEUE_SERIAL或者NULL為串行,DISPATCH_QUEUE_CONCURRENT為并行。
dispatch_get_main_queue()獲得主隊(duì)列
dispatch_get_global_queue(long identifier, unsigned long flags);獲得全局并發(fā)隊(duì)列.參數(shù)1為隊(duì)列的優(yōu)先級。參數(shù)2暫時(shí)沒有用,填0即可。
全局隊(duì)列 、 并發(fā)隊(duì)列的區(qū)別:
全局隊(duì)列沒有名稱,無論 MRC & ARC 都不需要考慮釋放.
并發(fā)隊(duì)列有名字,和 NSThread 的 name 屬性作用類似;如果在 MRC 開發(fā)時(shí),需要使用 dispatch_release(q); 釋放相應(yīng)的對象;dispatch_barrier 必須使用自定義的并發(fā)隊(duì)列;開發(fā)第三方框架時(shí),建議使用并發(fā)隊(duì)列
- 任務(wù)
任務(wù)的執(zhí)行方式:同步、異步。同步異步的區(qū)別是,是否會(huì)阻塞當(dāng)前線程。
同步執(zhí)行會(huì)阻塞當(dāng)前線程,等到block任務(wù)執(zhí)行完畢,然后當(dāng)前線程再繼續(xù)往下運(yùn)行。異步執(zhí)行不會(huì)阻塞當(dāng)前線程。通常的表現(xiàn)是:同步是在當(dāng)前線程中執(zhí)行,異步是在另一條線程中執(zhí)行,但也有例外。
通過打斷點(diǎn)來看同步異步的運(yùn)行過程來理解會(huì)更直觀。
看下面的例子:
串行隊(duì)列,同步執(zhí)行
- (void)demo1{
//創(chuàng)建串行隊(duì)列
//參數(shù)1:隊(duì)列的名字 參數(shù)2:隊(duì)列的屬性
dispatch_queue_t serialQueue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10 ;i++ ){
dispatch_sync(serialQueue, ^{
NSLog(@"serialQueue %@ %d",[NSThread currentThread],i);
});
}
//用途:在多個(gè)線程時(shí),要確保在一個(gè)線程執(zhí)行完任務(wù)再去執(zhí)行另一個(gè)線程上的任務(wù)
}

運(yùn)行結(jié)果顯示:不開線程,在當(dāng)前線程下執(zhí)行(阻塞當(dāng)前線程);任務(wù)是有序執(zhí)行的。
串行隊(duì)列,異步執(zhí)行
- (void)demo2{
dispatch_queue_t serialQueue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10 ;i++ ){
dispatch_async(serialQueue, ^{
NSLog(@"serialQueue %@ %d",[NSThread currentThread],i);
});
}
}

運(yùn)行結(jié)果顯示:另外開一個(gè)線程(不會(huì)阻塞當(dāng)前線程);任務(wù)是有序執(zhí)行的。
修改demo1 demo2不進(jìn)行循環(huán)創(chuàng)建,通過打斷點(diǎn)來看同步異步的區(qū)別:異步,當(dāng)代碼塊執(zhí)行時(shí)demo2:方法已經(jīng)退出了;同步,demo1:一直等到代碼塊執(zhí)行完才退出
并行隊(duì)列,異步執(zhí)行
- (void)demo3{
//創(chuàng)建并行隊(duì)列
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
for (int i =0; i<10; i++) {
dispatch_async(concurrentQueue, ^{
NSLog(@"concurrent %@ %d",[NSThread currentThread],i);
});
}
}

運(yùn)行結(jié)果顯示:開多個(gè)線程(不會(huì)阻塞當(dāng)前線程),任務(wù)無序執(zhí)行。效率最大。每次開啟多少個(gè)線程是不固定的(線程數(shù)由GCD來決定)
并行隊(duì)列,同步執(zhí)行
- (void)demo4{
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
for (int i =0; i<10; i++) {
dispatch_sync(concurrentQueue, ^{
NSLog(@"concurrent %@ %d",[NSThread currentThread],i);
});
}
}

運(yùn)行結(jié)果顯示:不開線程,在當(dāng)前線程下執(zhí)行(當(dāng)前線程不一定是主線程);任務(wù)是有序執(zhí)行的。這種情況等同于,串行隊(duì)列同步執(zhí)行
主隊(duì)列,異步執(zhí)行
- (void)demo5{
//得到主隊(duì)列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
for (int i =0; i<10; i++) {
dispatch_async(mainQueue, ^{
NSLog(@"mainQueue %@",[NSThread currentThread]);
});
}
}

主隊(duì)列是特殊的串行隊(duì)列,永遠(yuǎn)在主線程執(zhí)行。運(yùn)行結(jié)果顯示:任務(wù)順序執(zhí)行,由于是異步,因此并不會(huì)阻塞當(dāng)前線程(主線程)。
主隊(duì)列,同步執(zhí)行
- (void)demo6{
NSLog(@"開始");
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_sync(mainQueue, ^{
NSLog(@"mainQueue %@",[NSThread currentThread]);//這個(gè)任務(wù)要等待-demo6執(zhí)行完成才能繼續(xù)執(zhí)行
});
NSLog(@"結(jié)束");
}
運(yùn)行的結(jié)果顯示,只會(huì)打印出“開始”這一句,然后后面主線程就卡死了。程序死鎖。
原因:同步執(zhí)行會(huì)阻塞當(dāng)前線程。當(dāng)前-demo6方法在主線程中執(zhí)行,它把block任務(wù)放到主隊(duì)列中執(zhí)行,即執(zhí)行到dispatch_sync()的時(shí)候,主線程就被阻塞了。主線程要等到任務(wù)執(zhí)行完成之后才會(huì)繼續(xù)往下執(zhí)行,而block任務(wù)要等到主線程中的-demo6方法執(zhí)行完之后才能執(zhí)行。由此造成死鎖,主線程卡死。
總結(jié):

線程間通信
dispatch_async(dispatch_get_global_queue(0, 0), ^{ //也叫后臺執(zhí)行
// 執(zhí)行耗時(shí)的異步操作...
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主線程,執(zhí)行UI刷新操作
});
});
隊(duì)列組
隊(duì)列組可以將很多隊(duì)列添加到一個(gè)組里,當(dāng)這個(gè)組里所有的任務(wù)都執(zhí)行完了,隊(duì)列組會(huì)通過一個(gè)方法通知我們。
假設(shè)現(xiàn)在有這樣一個(gè)需求:分別異步執(zhí)行兩個(gè)耗時(shí)操作;等兩個(gè)異步操作都執(zhí)行完畢,再回到主線程執(zhí)行操作。這時(shí)可以使用隊(duì)列組來高效實(shí)現(xiàn)。
-(void)demo{
NSLog(@"begin");
//創(chuàng)建組
dispatch_group_t group = dispatch_group_create();
//開啟異步任務(wù),參數(shù)1:隊(duì)列組;參數(shù)2:隊(duì)列;參數(shù)3:任務(wù)
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:arc4random_uniform(5)];
NSLog(@"下載 文件1.zip %@",[NSThread currentThread]);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:arc4random_uniform(5)];
NSLog(@"下載 文件2.zip %@",[NSThread currentThread]);
});
//完成隊(duì)列組的任務(wù)后進(jìn)行通知,參數(shù)1:隊(duì)列組;參數(shù)2:隊(duì)列;參數(shù)3:任務(wù)
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"下載完成 %@",[NSThread currentThread]);
});
}
一次性執(zhí)行
被調(diào)用多次,但只會(huì)執(zhí)行一次。它是線程安全的
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// for (int i = 0; i < 20; i++) {
// [self demo];
// }
//多線程測試
for (int i = 0; i < 20; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self demo];
});
}
}
-(void)demo{
NSLog(@"這句話執(zhí)行多次");
static dispatch_once_t onceToken;
NSLog(@"%ld",onceToken);
dispatch_once(&onceToken, ^{
NSLog(@"once");
});
}
dispatch_once_t 實(shí)際上被定義為long類型,demo方法中打印出onceToken的值中可以看出,在代碼第一次被調(diào)用的時(shí)候,onceToken的值為0(即使在多線程下可能會(huì)有多個(gè)0值打印出來),此時(shí)block里面的代碼會(huì)被執(zhí)行而且只會(huì)執(zhí)行一次。當(dāng)代碼再次被調(diào)用,onceToken的值為非0,block代碼不會(huì)被執(zhí)行。onceToken值相當(dāng)于是一個(gè)flag,用來標(biāo)記block里面的代碼是否已經(jīng)被執(zhí)行過。
單例模式
OC里面實(shí)現(xiàn)單例在此不進(jìn)行詳述,用到了 GCD 的 dispatch_once方法。
實(shí)現(xiàn)單例還可以用互斥鎖,但相比GCD性能要差得多(沒有得到鎖的線程一直在等待。)
@implementation Singleton
+(instancetype)configSync{
static Singleton *instance;
@synchronized(self) {
if(instance == nil){
instance = [[Singleton alloc] init];
}
}
return instance;
}
@end
NSOperation
面向?qū)ο?,基于GCD封裝,比GCD多一些簡單的功能,基于OC語言。線程的生命周期是自動(dòng)管理的,經(jīng)常使用。
NSOperation 和NSOperationQueue就相當(dāng)于是GCD 的任務(wù)和隊(duì)列。
NSOperation實(shí)現(xiàn)多線程的具體步驟
1.將需要執(zhí)行的任務(wù)封裝到一個(gè)NSOperation對象中
2.將NSOperation對象添加到NSOperationQueue中
3.系統(tǒng)會(huì)自動(dòng)將NSOperationQueue中的NSOperation取出來
4.NSOperation封裝的任務(wù)被放到一條新線程中執(zhí)行
任務(wù)NSOperation
NSOperation是一個(gè)抽象類,不具備封裝任務(wù)的能力,不可以直接使用,它只是約束子類都具有共同的屬性和方法。因此必須使用它的子類:
1.NSInvocationOperation
2.NSBlockOperation
3.自定義子類,繼承NSOperation,實(shí)現(xiàn)內(nèi)部相應(yīng)的方法
子類創(chuàng)建operation使用的方法雖不盡相同,但最后都需要調(diào)用start方法來啟動(dòng)執(zhí)行任務(wù)。默認(rèn)情況下,調(diào)用start方法后不會(huì)新開一個(gè)線程執(zhí)行任務(wù),而是在當(dāng)前線程同步執(zhí)行,只有將NSOperation放入一個(gè)NSOperationQueue中,才會(huì)異步執(zhí)行操作。
- NSInvocationOperation比較少用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//創(chuàng)建operation
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(demo1) object:nil];//任務(wù)在主線程(當(dāng)前線程)運(yùn)行
//啟動(dòng)任務(wù)
[op start];
}
- (void)demo1{
NSLog(@"任務(wù)1當(dāng)前線程----%@",[NSThread currentThread]);//在主線程運(yùn)行
}
- NSBlockOperation
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//創(chuàng)建operation
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"當(dāng)前線程----%@",[NSThread currentThread]);
}];//還是主線程
//額外的任務(wù)在子線程執(zhí)行
[op addExecutionBlock:^{
NSLog(@"當(dāng)前線程----%@",[NSThread currentThread]);
}];
[op start];
}

addExecutionBlock方法給operation添加額外的任務(wù),這時(shí)operation中的所有任務(wù)并發(fā)執(zhí)行(當(dāng)前線程和其他子線程)只要NSBlockOperation封裝的任務(wù)數(shù)大于1,就會(huì)異步執(zhí)行?
- 自定義子類
當(dāng)以上兩個(gè)子類無法滿足需求時(shí),又或者需要封裝任務(wù)等等,就需要自定義operation。
自定義operation需要繼承NSOperation類,并實(shí)現(xiàn)main方法,因?yàn)樵谡{(diào)用start方法的時(shí)候,內(nèi)部會(huì)調(diào)用main方法完成相關(guān)邏輯。重寫main方法的注意點(diǎn):
1.自己創(chuàng)建自動(dòng)釋放池(因?yàn)槿绻钱惒讲僮?,無法訪問主線程的自動(dòng)釋放池)【這個(gè)不太理解】
2.經(jīng)常通過-(BOOL)isCancelled方法檢測操作是否被取消,對取消作出響應(yīng)
具體舉例下面用到再細(xì)說。
隊(duì)列NSOperationQueue
NSOperation可以調(diào)用start方法來執(zhí)行任務(wù),但因?yàn)槭峭綀?zhí)行,會(huì)占用當(dāng)前線程。但如果把NSOperation添加到NSOperationQueue中,就可以異步執(zhí)行任務(wù)。
NSOperationQueue隊(duì)列類型:
1.主隊(duì)列[NSOperationQueue mainQueue]添加到主隊(duì)列中的任務(wù)都會(huì)放到主線程中執(zhí)行
2.其他隊(duì)列(包括串行、并發(fā)),使用alloc init方式創(chuàng)建。任務(wù)只要添加到隊(duì)列,系統(tǒng)就會(huì)自動(dòng)異步執(zhí)行任務(wù)(自動(dòng)調(diào)用start方法)。
用maxConcurrentOperationCount(最大并發(fā)數(shù))屬性控制是串行還是并發(fā)隊(duì)列添加任務(wù)的方法:
- (void)addOperation:(NSOperation *)op;
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;
- (void)operationQueueDemo1{
//將NSOperation添加到NSOperationQueue中,系統(tǒng)會(huì)自動(dòng)**異步執(zhí)行**任務(wù)
//創(chuàng)建隊(duì)列
NSOperationQueue *opqueue = [[NSOperationQueue alloc]init];
//創(chuàng)建operation
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(demo1) object:nil];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務(wù)2當(dāng)前線程----%@",[NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
NSLog(@"任務(wù)3當(dāng)前線程----%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務(wù)4當(dāng)前線程----%@", [NSThread currentThread]);
}];
CustomOperation *op4 = [[CustomOperation alloc] init];//自定義operation
// 添加任務(wù)到隊(duì)列中
[opqueue addOperation:op];//一旦添加,就會(huì)執(zhí)行(自動(dòng)start),并發(fā),且每添加一個(gè)operation開一條子線程
[opqueue addOperation:op2];
[opqueue addOperation:op3];
[opqueue addOperation:op4];//添加自定義operation,自動(dòng)調(diào)用start,start調(diào)用main
//添加任務(wù)的另一種更簡潔的方式,和上面是等價(jià)的
[opqueue addOperationWithBlock:^{
NSLog(@"block添加任務(wù),block當(dāng)前線程----%@",[NSThread currentThread]);
}];
}

ps:在這里使用了自定義operation
#import "CustomOperation.h"
@implementation CustomOperation
//封裝自定義任務(wù),自動(dòng)調(diào)用這個(gè)方法
- (void)main
{
NSLog(@"自定義operation");
}
@end
-
隊(duì)列最大并發(fā)數(shù)
@property NSInteger maxConcurrentOperationCount;這個(gè)屬性用來設(shè)置最多可以讓多少個(gè)任務(wù)同時(shí)執(zhí)行。所以,把maxConcurrentOperationCount的值設(shè)為1,那么隊(duì)列就是串行的了!
- (void)operationQueueDemo2{
NSOperationQueue *opqueue = [[NSOperationQueue alloc]init];
// opqueue.maxConcurrentOperationCount = 2;
//串行隊(duì)列
opqueue.maxConcurrentOperationCount = 1;
[opqueue addOperationWithBlock:^{
NSLog(@"block添加任務(wù),任務(wù)1當(dāng)前線程----%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
[opqueue addOperationWithBlock:^{
NSLog(@"block添加任務(wù),任務(wù)2當(dāng)前線程----%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
[opqueue addOperationWithBlock:^{
NSLog(@"block添加任務(wù),任務(wù)3當(dāng)前線程----%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
[opqueue addOperationWithBlock:^{
NSLog(@"block添加任務(wù),任務(wù)4當(dāng)前線程----%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
[opqueue addOperationWithBlock:^{
NSLog(@"block添加任務(wù),任務(wù)5當(dāng)前線程----%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
}
maxConcurrentOperationCount=2時(shí)運(yùn)行結(jié)果:

maxConcurrentOperationCount=1時(shí)運(yùn)行結(jié)果:

任務(wù)執(zhí)行在哪個(gè)線程不是固定的。比如當(dāng)前最大并發(fā)數(shù)為2,在同一時(shí)間并發(fā)執(zhí)行任務(wù)的線程的確有2個(gè),但并不是固定一直都是這兩個(gè)線程去處理任務(wù)。當(dāng)一個(gè)線程執(zhí)行完任務(wù)后,由系統(tǒng)來決定銷毀它另開線程還是繼續(xù)使用這個(gè)線程來執(zhí)行任務(wù)
-
隊(duì)列的暫停、恢復(fù)、取消
暫停、恢復(fù):suspended屬性
要執(zhí)行suspended=yes時(shí)候,隊(duì)列掛起,但如果有任務(wù)還沒執(zhí)行完,那么這個(gè)任務(wù)將會(huì)繼續(xù)執(zhí)行到完成。而隊(duì)列中余下的任務(wù)就會(huì)被掛起不執(zhí)行。
當(dāng)suspended屬性設(shè)為No的時(shí)候,再繼續(xù)執(zhí)行余下的任務(wù)。
隊(duì)列取消:- (void)cancelAllOperations;
cancelAllOperations取消隊(duì)列所有的任務(wù),取消了就不會(huì)再恢復(fù)。This method calls the cancel method on all operations currently in the queue.同樣,如果有任務(wù)還沒執(zhí)行完,那么這個(gè)任務(wù)將會(huì)繼續(xù)執(zhí)行到完成。
如果自定義operation中有多個(gè)耗時(shí)的操作,建議在main方法中在每個(gè)耗時(shí)操作后判斷任務(wù)是否已經(jīng)被取消了,如果取消了,余下的耗時(shí)操作將不再執(zhí)行
#import "CustomOperation.h"
@implementation CustomOperation
- (void)main
{
for (NSInteger i = 0; i<1000; i++) {
NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);
}
if (self.isCancelled) return;
for (NSInteger i = 0; i<1000; i++) {
NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]);
}
if (self.isCancelled) return;
for (NSInteger i = 0; i<1000; i++) {
NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]);
}
if (self.isCancelled) return;
}
@end
- 依賴
NSOperation之間可以設(shè)置依賴來保證執(zhí)行順序。[op1 addDependency:op2]可以讓任務(wù)op1在任務(wù)op2之后執(zhí)行。
這種任務(wù)依賴是可以跨隊(duì)列的。不能添加相互依賴,會(huì)死鎖,比如 A依賴B,B依賴A。
- (void)operationQueueDemo4{
NSOperationQueue *opqueue = [[NSOperationQueue alloc] init];
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務(wù)1----%@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務(wù)2----%@", [NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務(wù)3----%@", [NSThread currentThread]);
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務(wù)4----%@", [NSThread currentThread]);
}];
//監(jiān)聽任務(wù)是否執(zhí)行完畢,這個(gè)block在任務(wù)完成之后執(zhí)行,而且它不在主線程上,也與它監(jiān)聽的任務(wù)不一定在同一個(gè)線程上
op4.completionBlock = ^{
NSLog(@"op4執(zhí)行完畢---%@", [NSThread currentThread]);
};
// 設(shè)置依賴,而且這種依賴是可以跨隊(duì)列的
[op2 addDependency:op3];
//添加完依賴再把任務(wù)加到隊(duì)列上
[opqueue addOperation:op];
[opqueue addOperation:op2];
[opqueue addOperation:op3];
[opqueue addOperation:op4];
}

線程間通信
[[[NSOperationQueue alloc] init] addOperationWithBlock:^{
.......
// 回到主線程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
......
}];
}];
其他
線程同步:
為了防止多個(gè)線程可能會(huì)訪問同一塊資源,引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問題,所采取的措施。
- 互斥鎖:給需要同步的代碼塊加一個(gè)互斥鎖,就可以保證每次只有一個(gè)線程訪問此代碼塊。
售票問題:
@property (nonatomic , assign)int tickets;
- (void)viewDidLoad {
[super viewDidLoad];
self.tickets = 5;
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(sellTicket) object:nil];
thread.name = @"窗口1";
[thread start];
NSThread *thread2 = [[NSThread alloc]initWithTarget:self selector:@selector(sellTicket) object:nil];
thread2.name = @"窗口2";
[thread2 start];
}
- (void)sellTicket{
while (YES) {
//模擬網(wǎng)絡(luò)延時(shí)
[NSThread sleepForTimeInterval:1];
if (self.tickets > 0) {
self.tickets = self.tickets - 1;
NSLog(@"%@ 剩余票數(shù)%d",[NSThread currentThread],self.tickets);
continue;
}
NSLog(@"賣完了");
break;
}
}
運(yùn)行結(jié)果:產(chǎn)生數(shù)據(jù)錯(cuò)亂問題

解決方法:使用互斥鎖(同步鎖)
格式:@synchronized(鎖對象){需要鎖定的代碼}
優(yōu)缺點(diǎn):能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問題。但是需要消耗大量的CPU資源。沒有得到鎖的線程一直在等待。
鎖可以是任意對象,默認(rèn)鎖是開著的。
@property (nonatomic , strong) NSObject *obj;
- (void)viewDidLoad {
[super viewDidLoad];
self.tickets = 5;
self.obj = [[NSObject alloc]init];//obj要初始化,鎖才有效
......(創(chuàng)建線程)
}
- (void)sellTicket{
while (YES) {
//模擬網(wǎng)絡(luò)延時(shí)
[NSThread sleepForTimeInterval:1];
//鎖是一個(gè)任意對象(任意對象都有一把鎖),默認(rèn)鎖是開著的
@synchronized(self.obj) {
if (self.tickets > 0) {
self.tickets = self.tickets - 1;
NSLog(@"%@ 剩余票數(shù)%d",[NSThread currentThread],self.tickets);
continue;
}
NSLog(@"賣完了");
break;
}
}
}
運(yùn)行結(jié)果:是線程安全的

注意:鎖定一份代碼只能用一把鎖,使用多把鎖無效。如果把上面的代碼改成下面這樣,同樣會(huì)產(chǎn)生數(shù)據(jù)錯(cuò)亂問題。因?yàn)楫?dāng)前while循環(huán)中每次循環(huán)都創(chuàng)建一把新的鎖,鎖默認(rèn)都是開著的。
while (YES) {
[NSThread sleepForTimeInterval:1];
NSObject *obj = [[NSObject alloc]init];
@synchronized(obj) {
......
}
NSLog(@"賣完了");
break;
}
}
atomic 和nonatomic:
nonatomic非原子屬性:多個(gè)線程可以同時(shí)賦值同時(shí)讀取。非線程安全的,適合內(nèi)存小的移動(dòng)設(shè)備。開發(fā)中建議把屬性聲明為nonatomic
atomic原子屬性:多個(gè)線程中只有一個(gè)線程能夠?qū)ψ兞抠x值(為setter加鎖),但多個(gè)線程可以同時(shí)讀取。線程安全,但需要消耗大量資源。原子屬性有自旋鎖
互斥鎖:如果發(fā)現(xiàn)其他線程正在執(zhí)行鎖定的代碼,線程休眠(就緒狀態(tài)),等其他線程開鎖后,線程被喚醒。
自旋鎖:如果發(fā)現(xiàn)其他線程正在執(zhí)行鎖定代碼,線程會(huì)用死循環(huán)的方式一致等待鎖定代碼完成。自旋鎖適合執(zhí)行不耗時(shí)的操作。
不使用互斥鎖,如果把tickets聲明為atomic,再執(zhí)行第一段“售票問題”代碼的話會(huì)怎么樣呢?@property (nonatomic , assign)int tickets;
運(yùn)行結(jié)果顯示,自旋鎖仍然會(huì)有線程安全問題。因?yàn)樗粚etter加鎖,對Getter不加鎖。
延時(shí)執(zhí)行:
方式1:NSTimer
NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(task) userInfo:nil repeats:NO];
方式2:
[self performSelector:@selector(task) withObject:nil afterDelay:1];
方法3:GCD
//參數(shù)1:延時(shí)的時(shí)間 dispatch_time生成時(shí)間 納秒為計(jì)時(shí)單位 精度高
//參數(shù)2:隊(duì)列
//參數(shù)3:任務(wù)
//異步執(zhí)行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"task");
});
相關(guān)文章:
Cocoa深入學(xué)習(xí):NSOperationQueue、NSRunLoop和線程安全
iOS多線程你看我就夠