線程:
英文:Thread
線程,有時被稱為輕量級進(jìn)程(Lightweight Process,LWP),是程序執(zhí)行流的最小單元。一個標(biāo)準(zhǔn)的線程由線程ID,當(dāng)前指令指針(PC),寄存器集合和堆棧組成。另外,線程是進(jìn)程中的一個實(shí)體,是被系統(tǒng)獨(dú)立調(diào)度和分派的基本單位,線程自己不擁有系統(tǒng)資源,只擁有一點(diǎn)兒在運(yùn)行中必不可少的資源,但它可與同屬一個進(jìn)程的其它線程共享進(jìn)程所擁有的全部資源。一個線程可以創(chuàng)建和撤消另一個線程,同一進(jìn)程中的多個線程之間可以并發(fā)執(zhí)行。由于線程之間的相互制約,致使線程在運(yùn)行中呈現(xiàn)出間斷性。線程也有就緒、阻塞和運(yùn)行三種基本狀態(tài)。就緒狀態(tài)是指線程具備運(yùn)行的所有條件,邏輯上可以運(yùn)行,在等待處理機(jī);運(yùn)行狀態(tài)是指線程占有處理機(jī)正在運(yùn)行;阻塞狀態(tài)是指線程在等待一個事件(如某個信號量),邏輯上不可執(zhí)行。每一個程序都至少有一個線程,若程序只有一個線程,那就是程序本身。
線程是程序中一個單一的順序控制流程。進(jìn)程內(nèi)一個相對獨(dú)立的、可調(diào)度的執(zhí)行單元,是系統(tǒng)獨(dú)立調(diào)度和分派CPU的基本單位指運(yùn)行中的程序的調(diào)度單位。在單個程序中同時運(yùn)行多個線程完成不同的工作,稱為多線程。 -----百度百科
ios中實(shí)現(xiàn)多線程的幾種方式:
- Pthreads(不用)
- NSThread(用一部分,其中幾個比較方便的方法)
- GCD(常用)
- NSOperation&NSOperationQueue(看需求)
- Pthreads
pthread 是 POSIX 多線程開發(fā)框架,是基于C 語言的跨平臺框架。沒用過,不了解,感興趣的同學(xué)可以自己度娘下。
- NSThread
NSThread是基于Thread使用,輕量級的多線程編程方法,一個NSThread對象代表一個線程,需要手動管理線程的生命周期,處理線程同步等問題。所以一般只使用其中幾個方法,方便調(diào)試線程。
[NSThread isMainThread]; // 是否主線程
[NSThread currentThread]; // 當(dāng)前線程
- GCD
Apple基本c++開發(fā)的一套多線程處理技術(shù),自動管理生命周期。
任務(wù)與隊(duì)列
任務(wù):你要執(zhí)行的操作,GCD將任務(wù)放在block中。執(zhí)行任務(wù)有2中方式,同步執(zhí)行,異步執(zhí)行。
- 同步
不具備開啟線程的能力,會阻塞當(dāng)前線程。 - 異步
具備開啟新線程的能力,不會阻塞當(dāng)前線程。
隊(duì)列:用來存放任務(wù)的隊(duì)列,是一種特殊的線性表,采用FIFO(先進(jìn)先出)的原則,則從頂部開始讀取任務(wù),從尾部加入任務(wù)到隊(duì)列。在GCD中有3種隊(duì)列:串行隊(duì)列,并行隊(duì)列,主隊(duì)列(特殊的串行隊(duì)列)。
- 串行隊(duì)列
一個一個任務(wù)有序執(zhí)行,上一個任務(wù)沒執(zhí)行完畢,下一個任務(wù)不會執(zhí)行。 - 并行隊(duì)列
同時執(zhí)行多個任務(wù),不用等待上一個任務(wù)執(zhí)行完畢。 - 主隊(duì)列
和串行隊(duì)列一樣,需要等待上一個任務(wù)執(zhí)行完成,才能執(zhí)行下一個任務(wù)。
創(chuàng)建隊(duì)列:
- 全局并行隊(duì)列(系統(tǒng)自帶的,全局唯一)
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- 自定義并行隊(duì)列:
dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
- 自定義串行隊(duì)列:
dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
- 主隊(duì)列:
dispatch_get_main_queue()
創(chuàng)建任務(wù):
- 同步任務(wù)
dispatch_sync(隊(duì)列, ^{要執(zhí)行的任務(wù)});
- 異步任務(wù)
dispatch_async(隊(duì)列, ^{要執(zhí)行的任務(wù)});
基本使用:
- 同步任務(wù)+串行隊(duì)列(不會開啟新線程,在當(dāng)前線程中執(zhí)行任務(wù))
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"當(dāng)前線程----->%@",[NSThread currentThread]);
});
- 同步任務(wù)+并行隊(duì)列(不會開啟新線程,在當(dāng)前線程中執(zhí)行任務(wù))
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"當(dāng)前線程----->%@",[NSThread currentThread]);
});
- 同步任務(wù)+主隊(duì)列(不會開啟新線程,在主線程中執(zhí)行任務(wù))
dispatch_sync(dispatch_get_main_queue(), 0), ^{
NSLog(@"當(dāng)前線程----->%@",[NSThread currentThread]);
});
- 異步任務(wù)+串行隊(duì)列(會開啟一條新的線程,串行執(zhí)行任務(wù))
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"當(dāng)前線程----->%@",[NSThread currentThread]);
});
- 異步任務(wù)+并行隊(duì)列(會開啟至少一條新的線程,并行執(zhí)行任務(wù))
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"當(dāng)前線程----->%@",[NSThread currentThread]);
});
- 異步任務(wù)+主隊(duì)列(不會開啟新的線程,會在主線程中執(zhí)行任務(wù))
dispatch_async(dispatch_get_main_queue(), 0), ^{
NSLog(@"當(dāng)前線程----->%@",[NSThread currentThread]);
});
總結(jié):
1.同步任務(wù)都不會開啟新的線程,所以會阻塞當(dāng)前線程。
2.主隊(duì)列中的任務(wù)不會開啟新的線程,會在主線程中執(zhí)行。
3.異步任務(wù)+串行隊(duì)列,會開啟一條新的線程,在新的線程串行執(zhí)行任務(wù)。
4.異步任務(wù)+并行多列,會開啟至少一條新的線程,在新的線程中并發(fā)執(zhí)行任務(wù)。
線程阻塞:
實(shí)例1:
NSLog(@"當(dāng)前線程----->%@",[NSThread currentThread]); // 會打印
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"當(dāng)前線程----->%@",[NSThread currentThread]); // 這句話永遠(yuǎn)不會打印,此時主線程已經(jīng)阻塞了,你對界面的所有操作都沒反應(yīng)了。
});
NSLog(@"當(dāng)前線程----->%@",[NSThread currentThread]); // 不會打印
原因:
1.dispatch_sync同步任務(wù),不會開啟新的線程,所以上面的代碼是在主線程中執(zhí)行的,也就會阻塞主線程,等待block中的任務(wù)完成。
2.dispatch_get_main_queue()主隊(duì)列,會把block中的任務(wù)放進(jìn)主隊(duì)列,也就是主線程中去執(zhí)行,可是此時主線程已經(jīng)阻塞了,block永遠(yuǎn)無法完成任務(wù)。所以就會一直阻塞主線程。
實(shí)例2:
// 自定義串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
// 串行異步
dispatch_async(queue, ^{ // @1
NSLog(@"當(dāng)前線程----->%@",[NSThread currentThread]); // 會打印
dispatch_sync(queue, ^{ // 不會打印 @2
NSLog(@"當(dāng)前線程1----->%@",[NSThread currentThread]);
});
NSLog(@"當(dāng)前線程2----->%@",[NSThread currentThread]); // 不會打印
});
NSLog(@"當(dāng)前線程3----->%@",[NSThread currentThread]); // 會打印 @3
原因:
1.上面的代碼@1會開啟一條新的線程,但因?yàn)槭窃诖嘘?duì)列中,所以會一個一個執(zhí)行任務(wù)。我們假設(shè)開啟的新線程叫“B”;
2.打印完"當(dāng)前線程"后,@2同步任務(wù),不會開啟新的線程,會阻塞當(dāng)前線程,所以還是在"B"線程中執(zhí)行任務(wù),此時線程"B"已經(jīng)阻塞了,@2會把block當(dāng)中的任務(wù)放入"myQueue"中去執(zhí)行,但是"myQueue"是串行的,所以必須等"myQueue"執(zhí)行完上一個任務(wù),而它執(zhí)行的上一個任務(wù)就是當(dāng)前block中的任務(wù),也就是阻塞了的@2,@2永遠(yuǎn)執(zhí)行不了,所以會一直阻塞。
3.@3會打印,是因?yàn)樗窃谥骶€程中的。
實(shí)例3:
// 創(chuàng)建并行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{ // @1
NSLog(@"當(dāng)前線程----->%@",[NSThread currentThread]);
dispatch_sync(queue, ^{ // @2
NSLog(@"當(dāng)前線程1----->%@",[NSThread currentThread]);
});
NSLog(@"當(dāng)前線程2----->%@",[NSThread currentThread]); // @3
});
NSLog(@"當(dāng)前線程3----->%@",[NSThread currentThread]); // @4
-----------------------------以上都會打印-----------------------------
原因:
1.@1會開啟至少一條新的線程,并行執(zhí)行任務(wù)。
2.@2不會開啟新的線程,在當(dāng)前線程并行執(zhí)行任務(wù)。會阻塞當(dāng)前線程,但因?yàn)槭遣⑿嘘?duì)列中,所以會執(zhí)行完@2,在執(zhí)行@3.
3.@3在主線程中執(zhí)行,不受影響。
隊(duì)列組
// 創(chuàng)建組
dispatch_group_t group = dispatch_group_create();
// 系統(tǒng)全局唯一并行隊(duì)列
//dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 自定義串行隊(duì)列,按順序執(zhí)行
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
dispatch_group_async(group, queue, ^{ // @1
NSLog(@"當(dāng)前線程----->%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{ // @2
NSLog(@"當(dāng)前線程1----->%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{ // @3
NSLog(@"當(dāng)前線程2----->%@",[NSThread currentThread]);
});
dispatch_group_notify(group, queue, ^{ // @4
NSLog(@"當(dāng)前線程3----->%@",[NSThread currentThread]);
});
- 并行隊(duì)列
@1,2,3會隨機(jī)打印,最后打印@4- 串行隊(duì)列
@1<@2<@3<@4 按順序打印
單列
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
延時執(zhí)行
// 如果在串行、并行隊(duì)列中執(zhí)行,會開啟線程。也就是說dispatch_after方法是異步執(zhí)行的
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"當(dāng)前線程----->%@",[NSThread currentThread]);
});
柵欄方法(分割任務(wù))
// dispatch_barrier_async 方法需使用自定義隊(duì)列,不能使用系統(tǒng)全局隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
//dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{ // @1
NSLog(@"當(dāng)前線程----->%@",[NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{ // @2
NSLog(@"當(dāng)前線程1----->%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{ // @3
NSLog(@"當(dāng)前線程2----->%@",[NSThread currentThread]);
});
注意:
使用dispatch_barrier_async時:
1.必須使用自定義隊(duì)列,不能使用系統(tǒng)全局隊(duì)列。
dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT)
NSOperation&NSOperationQueue
NSOperation是Apple對GCD的封裝,是面向?qū)ο蟮?。NSOperation、NSOperationQueue分別對應(yīng)GCD中的任務(wù)和隊(duì)列。
注意:NSOperation是個抽象類,不能直接使用,必須使用它的2個子類:NSInvocationOperation、NSBlockOperation。
創(chuàng)建任務(wù):
- NSInvocationOperation
NSInvocationOperation *invocationOP = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
[invocationOP start];
- NSBlockOperation
NSBlockOperation *blockOP = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"當(dāng)前線程---->%@",[NSThread currentThread]);
}];
[blockOP addExecutionBlock:^{
NSLog(@"當(dāng)前線程1---->%@",[NSThread currentThread]);
}];
[blockOP start];
注意:
1.addExecutionBlock方法可能開啟新的線程,也可能在主線程中執(zhí)行。
2.addExecutionBlock方法調(diào)用必須在start方法之前,否則會報(bào)錯。
- 自定義任務(wù):新建一個類繼承NSOperation,需要重寫main,cancel,finished,executing等方法。
創(chuàng)建隊(duì)列:(只有主隊(duì)列,和其他隊(duì)列,沒有串行和并行區(qū)分)
- NSOperationQueue(其他隊(duì)列)
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
- 主隊(duì)列
[NSOperationQueue mainQueue];
使用:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1; // 串行,默認(rèn)為-1不限制,既并行
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"當(dāng)前線程---->%@",[NSThread currentThread]);
}];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"當(dāng)前線程1---->%@",[NSThread currentThread]);
}];
[queue addOperation:operation];
[queue addOperation:operation1];
注意:任務(wù)加入隊(duì)列中,會自動執(zhí)行,不需要調(diào)用start方法,否則會報(bào)錯。
依賴
必須等A任務(wù)執(zhí)行完畢之后在執(zhí)行B任務(wù)。
比如從網(wǎng)上開啟一個線程下載圖片,必須等圖片下載完成之后在主線程加載圖片,刷新UI,這個時候就可以用上依賴了。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//queue.maxConcurrentOperationCount = 1; // 串行,默認(rèn)為-1不限制
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"當(dāng)前線程---->%@",[NSThread currentThread]);
}];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"當(dāng)前線程1---->%@",[NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"當(dāng)前線程2---->%@",[NSThread currentThread]);
}];
[operation addDependency:operation1]; // operation依賴operation1
[operation2 addDependency:operation]; // 2operation依賴operation
[queue addOperations:@[operation,operation1] waitUntilFinished:NO];
[NSOperationQueue.mainQueue addOperation:operation2];
2017-08-10 10:46:34.938 ZTSlidingVC[1273:222606] 當(dāng)前線程1----><NSThread: 0x7fd9f3dd9510>{number = 2, name = (null)}
2017-08-10 10:46:34.938 ZTSlidingVC[1273:222606] 當(dāng)前線程----><NSThread: 0x7fd9f3dd9510>{number = 2, name = (null)}
2017-08-10 10:46:34.948 ZTSlidingVC[1273:222557] 當(dāng)前線程2----><NSThread: 0x7fd9f3d287a0>{number = 1, name = main}
注意:依賴關(guān)系是可以跨隊(duì)列的,如上面例子所示。
其他屬性、方法
- NSOperation
屬性:
@property (readonly, getter=isCancelled) BOOL cancelled; // 是否取消任務(wù)
@property (readonly, getter=isExecuting) BOOL executing; // 是否正在執(zhí)行
@property (readonly, getter=isFinished) BOOL finished; // 是否完成任務(wù)
方法:
- (void)cancel; // 取消任務(wù)
- (void)start; // 開始任務(wù)
- (void)addDependency:(NSOperation *)op; // 添加依賴
- (void)removeDependency:(NSOperation *)op; // 刪除依賴
- NSOperationQueue
屬性:
@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0); // 獲取隊(duì)列中的任務(wù)數(shù)量
@property NSInteger maxConcurrentOperationCount; // 設(shè)置最大任務(wù)數(shù)
@property (getter=isSuspended) BOOL suspended; // YES:暫停,NO:繼續(xù)(對正在執(zhí)行的任務(wù)無效,只是暫停調(diào)度新的任務(wù)執(zhí)行)
@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue NS_AVAILABLE(10_6, 4_0); // 獲取當(dāng)前隊(duì)列
@property (class, readonly, strong) NSOperationQueue *mainQueue NS_AVAILABLE(10_6, 4_0); // 獲取主隊(duì)列
方法:
- (void)cancelAllOperations; // 取消所有任務(wù)
- (void)waitUntilAllOperationsAreFinished; // 等待所有隊(duì)列中的任務(wù)執(zhí)行完成,會阻塞線程(在等待時,其他線程仍然可以往隊(duì)列中添加任務(wù))
線程同步
- 為什么需要線程同步:
當(dāng)多個線程同時訪問一個統(tǒng)一資源,造成數(shù)據(jù)狀態(tài)不一致,產(chǎn)生的數(shù)據(jù)混亂,安全等問題。 - 實(shí)現(xiàn)線程同步的2種方式:
1.加鎖
- @synchronized 關(guān)鍵字加鎖
- NSLock 對象鎖
- NSCondition
- NSConditionLock 條件鎖
- NSRecursiveLock 遞歸鎖
- pthread_mutex 互斥鎖(C語言)
- dispatch_semaphore 信號量實(shí)現(xiàn)加鎖(GCD)
- OSSpinLock
方法有點(diǎn)多,這就不一一介紹了,開發(fā)中也用不了這么多。這里就簡單介紹一下1,2,7的使用把,需要其他更詳細(xì)的的功能請自行g(shù)oolge。
- @synchronized 關(guān)鍵字加鎖(性能較差,使用簡單)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized (self) {
NSLog(@"做你想做的事");
}
});
- NSLock(性能一般)
NSLock *lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if ([lock tryLock]) { // 嘗試加鎖,如果失敗了,并不會阻塞線程,只是立即返回NO
NSLog(@"做你想做的事");
[lock unlock]; // 記得解鎖
}
});
- dispatch_semaphore 信號量實(shí)現(xiàn)加鎖(GCD,推薦使用此方法)
dispatch_semaphore_create 創(chuàng)建一個semaphore
dispatch_semaphore_signal 發(fā)送一個信號(計(jì)數(shù)器+1)
dispatch_semaphore_wait 等待信號(信號量-1,如果信號量<=0,則一直等待,會阻塞線程)
dispatch_semaphore_t dsema = dispatch_semaphore_create(2); // 創(chuàng)建信號量,后面的數(shù)字既最大并發(fā)量
for(int i=0; i<10; i++){
dispatch_semaphore_wait(dsema, DISPATCH_TIME_FOREVER); // -1 DISPATCH_TIME_FOREVER會一直等待,直到信號量大于0。DISPATCH_TIME_NOW不等待,也就不能控制線程并發(fā)數(shù)了。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"------>%d 當(dāng)前線程---->%@",i,[NSThread currentThread]);
dispatch_semaphore_signal(dsema); // +1
});
}
上面的列子,看起來創(chuàng)建了10個線程,其實(shí)同時只有2個線程在并發(fā)執(zhí)行。
2.使用串行隊(duì)列
參考:
http://m.itdecent.cn/p/0b0d9b1f1f19
http://ksnowlv.github.io/blog/2014/09/07/ios-tong-bu-suo-xing-neng-dui-bi/