一、一些基本概念
1、線程狀態
注:線程的死亡狀態代表線程任務執行完畢,正常退出。或者手動調用[NSThread exit]手動退出。
2、線程同步和異步
二者的根本區別在于有沒有開辟新線程的能力,同步會阻塞當前線程,異步不會阻塞當前線程。但是使用異步不一定開辟了新的線程,如下表:
區別 | 并發隊列 | 串行隊列 | 主隊列 |
---|---|---|---|
同步 | 沒有開啟新線程,串行執行任務 | 沒有開啟新線程,串行執行任務 | 主線程調用:死鎖卡住不執行。其他線程調用:沒有開啟新線程,串行執行任務 |
異步 | 有開啟新線程,并發執行任務 | 有開啟新線程(1條),串行執行任務 | 沒有開啟新線程,串行執行任務 |
3、并行和串行
并行是指隊列中的任務執行順序不固定,單核CPU下,通過調度算法分片執行,多核CPU下,任務可以同時執行。
串行是指隊列中的任務按照添加順序執行
4、線程安全
多個線程操作同一塊資源的時候,為了避免數據發生混亂,需要注意線程安全。可以通過加鎖解決:
- @synchronized(){},互斥鎖。
- NSLock
- NSConditionLock 條件鎖 可以設置條件
#import "NSLockTest.h"
@interface NSLockTest()
@property (nonatomic,strong) NSMutableArray *tickets;
@property (nonatomic,assign) int soldCount;
@property (nonatomic,strong) NSConditionLock *condition;
@end
@implementation NSLockTest
- (void)forTest
{
self.tickets = [NSMutableArray arrayWithCapacity:1];
self.condition = [[NSConditionLock alloc]initWithCondition:0];
NSThread *windowOne = [[NSThread alloc]initWithTarget:self selector:@selector(soldTicketOne) object:nil];
[windowOne start];
NSThread *windowTwo = [[NSThread alloc]initWithTarget:self selector:@selector(soldTicketTwo) object:nil];
[windowTwo start];
NSThread *windowTuiPiao = [[NSThread alloc]initWithTarget:self selector:@selector(tuiPiao) object:nil];
[windowTuiPiao start];
}
//一號窗口
-(void)soldTicketOne
{
while (YES) {
NSLog(@"====一號窗口沒票了,等別人退票");
[self.condition lockWhenCondition:1];
NSLog(@"====在一號窗口買了一張票,%@",[self.tickets objectAtIndex:0]);
[self.tickets removeObjectAtIndex:0];
[self.condition unlockWithCondition:0];
}
}
//二號窗口
-(void)soldTicketTwo
{
while (YES) {
NSLog(@"====二號窗口沒票了,等別人退票");
[self.condition lockWhenCondition:2];
NSLog(@"====在二號窗口買了一張票,%@",[self.tickets objectAtIndex:0]);
[self.tickets removeObjectAtIndex:0];
[self.condition unlockWithCondition:0];
}
}
- (void)tuiPiao
{
while (YES) {
sleep(3);
[self.condition lockWhenCondition:0];
[self.tickets addObject:@"南京-北京(退票)"];
int x = arc4random() % 2;
if (x == 1) {
NSLog(@"====有人退票了,趕快去一號窗口買");
[self.condition unlockWithCondition:1];
}else
{
NSLog(@"====有人退票了,趕快去二號窗口買");
[self.condition unlockWithCondition:2];
}
}
}
@end
- NSRecursiveLock 遞歸鎖
//普通線程鎖,會造成死鎖
NSLock *lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^block)(int);
block = ^(int value) {
//加鎖
[lock lock];
if (value > 0) {
NSLog(@"%d",value);
sleep(2);
//遞歸調用
block(--value);
}
//解鎖
[lock unlock];
};
//調用代碼塊
block(10);
});
//遞歸鎖,不會造成死鎖
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^block)(int);
block = ^(int value) {
//加鎖
[lock lock];
if (value > 0) {
NSLog(@"%d",value);
sleep(2);
//遞歸調用
block(--value);
}
//解鎖
[lock unlock];
};
//調用代碼塊
block(10);
});
- dispatch_semaphore:性能第二好
- pthread_mutex:互斥鎖,性能最好
pthread_mutex_t _lock;
- (NSDictionary *)framePropertiesAtIndex:(NSUInteger)index {
NSDictionary *result = nil;
pthread_mutex_lock(&_lock);
result = [self _framePropertiesAtIndex:index];
pthread_mutex_unlock(&_lock);
return result;
}
5、線程通訊
兩種情況:
- 子線程切換到主線程
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主線程,執行UI刷新操作
}
[self performSelectorOnMainThread:@selector(displayImage:) withObject:image waitUntilDone:YES];
[self performSelector:@selector(displayImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// 回到主線程,執行UI刷新操作
}];
- 主線程切換到子線程
6、線程和進程的關系
二、performSelector方法集
1、
這三個方法,均為同步執行,與線程無關,主線程和子線程中均可調用成功。等同于直接調用該方法。在需要動態的去調用方法的時候去使用。
例如:[self performSelector:@selector(test2)];與[self test2];執行效果上完全相同。
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
2、
這兩個方法為異步執行,即使delay傳參為0,仍為異步執行。只能在主線程中執行,在子線程中不會調到aSelector方法。可用于當點擊UI中一個按鈕會觸發一個消耗系統性能的事件,在事件執行期間按鈕會一直處于高亮狀態,此時可以調用該方法去異步的處理該事件,就能避免上面的問題。
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
在方法未到執行時間之前,取消方法為:
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;
注意:調用該方法之前或在該方法所在的viewController生命周期結束的時候去調用取消函數,以確保不會引起內存泄露。
3、
這兩個方法,在主線程和子線程中均可執行,均會調用主線程的aSelector方法;
如果設置wait為YES:等待當前線程執行完以后,主線程才會執行aSelector方法;
設置為NO:不等待當前線程執行完,就在主線程上執行aSelector方法。
如果,當前線程就是主線程,那么aSelector方法會馬上執行。
注意:apple不允許程序員在主線程以外的線程中對ui進行操作,此時我們必須調用performSelectorOnMainThread函數在主線程中完成UI的更新。
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
4、
調用指定線程中的某個方法。分析效果同3。
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
5、
開啟子線程在后臺運行
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
三、NSThread
1、創建NSThread線程的方法
- 第一種方法:
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(task) object:nil];
[thread setName:@"創建線程,需要調用 start 方法"];
[thread start];
- 第二種方法:
[NSThread detachNewThreadSelector:@selector(task) toTarget:self withObject:nil];
- 第三種方法:
[self performSelectorInBackground:@selector(task) withObject:nil];
- 第四種方法:
創建線程對象
// 開了子線程,但是沒有任務,需要告訴它任務是什么
// 自定義類,繼承NSThread
// 重寫main方法,在main方法里面封裝任務
NSThread *thread = [[CustomThread alloc] init];
[thread setName:@"創建自定義線程,需要調用 start 方法"];
[thread start];
總結:如果你想拿到線程對象,使用第一種或者第四種方法,但是需要手動調用start。如果想創建自啟線程,使用第二種或者第三種方法,但是拿不到線程對象。
2、踩坑1
- [thread cancel]和[NSThread exit];
- 調用實例對象的- (void)cancel;方法、并不會直接取消線程。其作用只是對應線程的cancel屬性設置為YES、線程依舊繼續執行。在此之后:
- thread.cancel為YES。
- thread.executing為YES。
- thread.finished為NO。(除非全執行完了)
- 我們需要在適當的位置執行[NSThread exit];才可以真正關閉線程、余下的代碼不會繼續執行。同時:
- thread.cancel為你設置的狀態(默認應該是NO)。
- thread.executing為NO。
- thread.finished為YES。
- 調用實例對象的- (void)cancel;方法、并不會直接取消線程。其作用只是對應線程的cancel屬性設置為YES、線程依舊繼續執行。在此之后:
3、最大并發數
- (void)concurrentNumber {
for (int a = 0; a < 1000; a ++) {
[self performSelectorInBackground:@selector(cn:) withObject:[NSNumber numberWithInt:a]];
}
}
- (void)cn:(NSNumber *)number {
NSLog(@"%@%@", number, [NSThread currentThread]);
}
log結果在1-1001不等,可以確定的是使用NSThread不支持限制最大并發數,但是如果超過某個閾值會error[NSThread start]: Thread creation failed with error 35)
4、Example
#import "NSThreadViewController.h"
@interface NSThreadViewController ()
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, assign) NSInteger ticketCount;
@end
@implementation NSThreadViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 100, 300, 200)];
[self.view addSubview:self.imageView];
[self test_six];
}
// 顯式創建線程,此方法需要調用 start 方法啟動線程,需要管理生命周期
- (void)test_one {
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(task) object:nil];
[thread setName:@"創建線程,需要調用 start 方法"];
[thread start];
}
- (void)test_two {
[NSThread detachNewThreadSelector:@selector(task) toTarget:self withObject:nil];
}
- (void)test_three {
[self performSelectorInBackground:@selector(task) withObject:nil];
}
// 線程阻塞
- (void)test_four {
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(task_preventThread) object:nil];
[thread start];
}
- (void)task {
[NSThread sleepForTimeInterval:2];
NSLog(@"%@", [NSThread currentThread]);
}
- (void)task_preventThread {
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
NSLog(@"%@", [NSThread currentThread]);
NSLog(@"%d", [NSThread isMainThread]);
NSLog(@"%@", [NSThread mainThread]);
}
// 下載圖片
- (void)test_five {
[NSThread detachNewThreadSelector:@selector(downloadImage:) toTarget:self withObject:@"https://ysc-demo-1254961422.file.myqcloud.com/YSC-phread-NSThread-demo-icon.jpg"];
}
- (void)downloadImage:(NSString *)urlString {
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
[self performSelectorOnMainThread:@selector(displayImage:) withObject:image waitUntilDone:YES];
}
- (void)displayImage:(UIImage *)image {
self.imageView.image = image;
}
// 線程同步
- (void)test_six {
self.ticketCount = 50;
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTicket) object:nil];
thread1.name = @"線程1";
[thread1 start];
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTicket) object:nil];
thread2.name = @"線程2";
[thread2 start];
}
- (void)sellTicket {
while (1) {
@synchronized (self) {
if (self.ticketCount > 0) {
self.ticketCount --;
NSLog(@"當前線程:%@,剩余票數:%ld", [NSThread currentThread].name, self.ticketCount);
} else {
NSLog(@"所有的票已經賣完");
break;
}
}
}
}
@end
四、NSOperation
1、自定義非并發 NSOperation
- 要點:重寫 main 函數,創建自動釋放池,如果操作未取消,就執行操作
#import <Foundation/Foundation.h>
@interface ZYOperation : NSOperation
@end
#import "ZYOperation.h"
@implementation ZYOperation
- (void)main {
// 這里獲取不到主線程的自動釋放池,所以創建自動釋放池自動釋放內部創建的對象
@autoreleasepool {
if (!self.isCancelled) {
// 這里寫上任務代碼
NSLog(@"%@", [NSThread currentThread]);
}
}
}
@end
2、自定義并發 NSOperation
- 要點:
- 定義 finishen、executing 實例變量,在 init 方法中初始化其值,并重寫 isConcurrent(必須重寫)、isFinished、isExecuting、start、main 方法。
- 在 start 方法中任務判斷是否被取消,如果已取消,手動出發 isFinished 的KVO,否則手動出發 Executing 的KVO,并使用 NSThread 后臺執行 main 函數。
#import <Foundation/Foundation.h>
@interface ZYConcurrentOperation : NSOperation {
BOOL executing;
BOOL finished;
}
@end
#import "ZYConcurrentOperation.h"
@implementation ZYConcurrentOperation
- (instancetype)init {
self = [super init];
if (self) {
finished = NO;
executing = NO;
}
return self;
}
- (BOOL)isFinished {
return finished;
}
- (BOOL)isExecuting {
return executing;
}
- (BOOL)isConcurrent {
return YES;
}
- (void)start {
if (self.isCancelled) {
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
[self willChangeValueForKey:@"isExecuting"];
executing = YES;
[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
[self didChangeValueForKey:@"isExecuting"];
}
- (void)main {
@autoreleasepool {
// 這里執行自定義任務
NSLog(@"%@", [NSThread currentThread]);
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
finished = NO;
executing = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
}
@end
3、創建的Operation是不是多線程執行的實驗:
// 多線程
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task) object:nil];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];
// 這里 block 會被回調
[operation setCompletionBlock:^{
NSLog(@"操作執行完成");
}];
// 多線程
NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task_1) object:nil];
operation1.queuePriority = NSOperationQueuePriorityLow;
NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task_2) object:nil];
operation2.queuePriority = NSOperationQueuePriorityHigh;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation1];
[queue addOperation:operation2];
// 非多線程
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
[operation start];
// 添加的操作數大于1時,可能是多線程
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
[operation start];
// 多線程
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];
4、依賴關系
- 依賴必須在操作被添加到隊列(確切來說應該是被執行)之前設置、否則無效
- 這個例子說明 NSOperationQueue 不同于 GCD 中隊列 FIFO 的規則,隊列中的執行順序與依賴關系和優先級有關
- 取消依賴關系[operation1 removeDependency:operation0];
- 操作的依賴關系與本身綁定、并不受限于同一個隊列。即使所執行的隊列不同、也可以完成依賴操作
NSBlockOperation *operation0 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@,0", [NSThread currentThread]);
}];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@,1", [NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@,2", [NSThread currentThread]);
NSLog(@"%@", NSOperationQueue.currentQueue);
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@,3", [NSThread currentThread]);
NSLog(@"%@", NSOperationQueue.mainQueue);
}];
[operation1 addDependency:operation0];
[operation2 addDependency:operation1];
[operation3 addDependency:operation2];
NSOperationQueue *queue = [NSOperationQueue new];
[queue addOperation:operation0];
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];
5、依賴關系和優先級的判定
- 先判定依賴關系,再執行優先級
- 下面代碼先執行任務3,再執行高優先級任務,最后執行低優先級任務
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
operationQueue.maxConcurrentOperationCount = 1;
NSBlockOperation *blockOperation1=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"低優先級任務");
}];
blockOperation1.queuePriority = NSOperationQueuePriorityLow;
NSBlockOperation *blockOperation2=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"高優先級任務");
sleep(1);
}];
blockOperation2.queuePriority = NSOperationQueuePriorityHigh;
NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務3");
}];
[blockOperation1 addDependency:blockOperation3];
[blockOperation2 addDependency:blockOperation3];
[operationQueue addOperation:blockOperation1];
[operationQueue addOperation:blockOperation2];
[operationQueue addOperation:blockOperation3];
6、依賴在添加進隊列之后雖然不能追加。但是可以對某操作進行追加addExecutionBlock、也可以延后操作的執行。
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
NSBlockOperation * blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"進入操作1");
sleep(3);
NSLog(@"操作1完成");
}];
NSBlockOperation * blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"進入依賴操作");
}];
[blockOperation2 addDependency:blockOperation1];
[operationQueue addOperation:blockOperation1];
[operationQueue addOperation:blockOperation2];
[blockOperation1 addExecutionBlock:^{
NSLog(@"進入追加操作");
sleep(5);
NSLog(@"追加操作完成");
}];
7、串行和并行的控制
- 通過maxConcurrentOperationCount,如果設置為1,則為串行,大于1,為并行
- maxConcurrentOperationCount值默認為-1,不指定其值得情況下,默認為并行,指定其值為0,則不會執行queue中添加的操作
NSBlockOperation *operation0 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@,0", [NSThread currentThread]);
}];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@,1", [NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@,2", [NSThread currentThread]);
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@,3", [NSThread currentThread]);
}];
NSArray <NSOperation *>*operationArray = [NSArray arrayWithObjects:operation0, operation1, operation2, operation3, nil];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 2;
[queue addOperations:operationArray waitUntilFinished:NO];
8、任務數組阻塞和單個任務阻塞
- 單個任務阻塞
- 阻塞當前線程、直到該操作執行完成
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
NSBlockOperation *blockOperation3=[NSBlockOperation blockOperationWithBlock:^{
sleep(3);
NSLog(@"操作3執行完畢");
}];
NSBlockOperation *blockOperation2=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"操作2開始執行");
[blockOperation3 waitUntilFinished];
NSLog(@"操作2執行完畢");
}];
[operationQueue addOperation:blockOperation2];
[operationQueue addOperation:blockOperation3];
- 任務數組阻塞
- 如果為YES。阻塞當前線程、直到隊列該次添加的所有操作全部執行完成。
- 如果為NO。就是批量添加操作而已
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
NSBlockOperation *blockOperation3=[NSBlockOperation blockOperationWithBlock:^{
sleep(3);
NSLog(@"操作3執行完畢");
}];
NSLog(@"添加操作");
[operationQueue addOperations:@[blockOperation3] waitUntilFinished:YES];
NSLog(@"添加完成");
9、主隊列([NSOperationQueue mainQueue])可以不可以修改最大并發數?主隊列下添加的操作、都會在主線程執行么?
- 不能、主隊列的最大并發數始終為1(自定義隊列默認為-1)、且修改無效。
- 默認狀況下是的、但也有例外(追加操作addExecutionBlock)。
10、NSOperationQueue的最大并發數
- 我測試了一下,創建100個任務時,最大值為66。
11、NSOperation的優勢
- 可添加完成的代碼塊,在操作完成后執行。
- 添加操作之間的依賴關系,方便的控制執行順序。
- 設定操作執行的優先級。
- 可以很方便的取消一個操作的執行。
- 使用 KVO 觀察對操作執行狀態的更改:isExecuteing、isFinished、isCancelled。
五、GCD
- GCD:是 Apple 開發的一個多核編程的較新的解決方法。它主要用于優化應用程序以支持多核處理器。(來自百度百科)
- 同步和異步:二者根本的區別是有沒有開辟新線程的能力,以及是否等待上一個任務執行完畢。順序執行和并發執行。
- 隊列:隊列是一種特殊的線性表,采用 FIFO(先進先出)的原則,即新任務總是被插入到隊列的末尾,而讀取任務的時候總是從隊列的頭部開始讀取。每讀取一個任務,則從隊列中釋放一個任務
1、GCD 優勢
- GCD 可用于多核的并行運算
- GCD 會自動利用更多的 CPU 內核(比如雙核、四核)
- GCD 會自動管理線程的生命周期(創建線程、調度任務、銷毀線程)
- 程序員只需要告訴 GCD 想要執行什么任務,不需要編寫任何線程管理代碼
2、隊列的創建
// 主隊列:用來執行主線程上的操作任務
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 創建串行隊列:第一個參數是隊列名稱,第二個參數是隊列類型(串行或并行)
dispatch_queue_t serialQueue = dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t _Nullable attr#>);
// 創建并行隊列的兩種方式
// 1、第一個參數是隊列優先級,第二個參數是預留的,傳0即可
dispatch_queue_t concurrentQueue1 = dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>);
//2、第一個參數是隊列名稱,第二個參數是隊列類型(串行或并行)
dispatch_queue_t concurrentQueue2 = dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t _Nullable attr#>);
3、GCD 的其他方法
柵欄函數:dispatch_barrier_async
dispatch_barrier_async函數會等待前邊追加到并發隊列中的任務全部執行完畢之后,再將指定的任務追加到該異步隊列中。然后在dispatch_barrier_async函數追加的任務執行完畢之后,異步隊列才恢復為一般動作,接著追加任務到該異步隊列并開始執行。
- (void)barrier {
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(queue, ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_barrier_async(queue, ^{
// 追加任務 barrier
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印當前線程
}
});
dispatch_async(queue, ^{
// 追加任務3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(queue, ^{
// 追加任務4
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印當前線程
}
});
}
GCD 延時執行方法:dispatch_after
- (void)after {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"asyncMain---begin");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2.0秒后異步追加任務代碼到主隊列,并開始執行
NSLog(@"after---%@",[NSThread currentThread]); // 打印當前線程
});
}
// 需要注意的是,dispatch_after并不是在指定的時間之后執行處理,而是在指定的時間之后追加到dispatch queue處理。此源碼的作用是,在指定的時間之后,追加到main dispatch queue處理;
dispatch_once
- (void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執行1次的代碼(這里面默認是線程安全的)
});
}
dispatch_group
- NSOperation的添加進隊列后可不可以追加依賴?GCD任務組添加監聽后可不可以追加任務?
NSOperation的依賴必須在添加進隊列(并且執行前)之前設置。(但是我們可以對某被依賴的操作進行追加addExecutionBlock以延緩調用)
GCD任務組則具備追加任務的功能。前提是監聽并未被觸發。
NSLog(@"..............start..............");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"第一組任務");
[NSThread sleepForTimeInterval:1];
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"第二組任務");
[NSThread sleepForTimeInterval:1];
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1];
});
NSLog(@"...............end...............");
}
/*
2019-02-23 19:48:35.942689+0800 Group[10209:459897] ..............start..............
2019-02-23 19:48:35.942921+0800 Group[10209:459897] ...............end...............
2019-02-23 19:48:35.943606+0800 Group[10209:459928] 第一組任務
2019-02-23 19:48:35.943636+0800 Group[10209:459930] 第二組任務
2019-02-23 19:48:36.947657+0800 Group[10209:459897] <NSThread: 0x600000a01340>{number = 1, name = main}
*/
- (void)test_2 {
NSLog(@"..............start..............");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"第一組任務");
[NSThread sleepForTimeInterval:1];
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"第二組任務");
[NSThread sleepForTimeInterval:1];
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"...............end...............");
}
/*
2019-02-23 19:47:56.637099+0800 Group[10196:459432] ..............start..............
2019-02-23 19:47:56.637425+0800 Group[10196:459471] 第一組任務
2019-02-23 19:47:56.637485+0800 Group[10196:459472] 第二組任務
2019-02-23 19:47:57.639444+0800 Group[10196:459432] ...............end...............
*/
- (void)test_3 {
NSLog(@"..............start..............");
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"第一組任務");
[NSThread sleepForTimeInterval:1];
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"第二組任務");
[NSThread sleepForTimeInterval:1];
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"%@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1];
});
NSLog(@"...............end...............");
}
/*
19-02-23 19:43:17.207690+0800 Group[10144:457044] ..............start..............
2019-02-23 19:43:17.207927+0800 Group[10144:457044] ...............end...............
2019-02-23 19:43:17.207967+0800 Group[10144:457104] 第二組任務
2019-02-23 19:43:17.207985+0800 Group[10144:457103] 第一組任務
2019-02-23 19:43:18.212495+0800 Group[10144:457044] <NSThread: 0x60000177a900>{number = 1, name = main}
*/
GCD 信號量:dispatch_semaphore
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
tasks = dataTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
tasks = uploadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
tasks = downloadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;
}
// 控制線程并發數1
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue",DISPATCH_QUEUE_SERIAL);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(4);
for (NSInteger i = 0; i < 15; i++) {
dispatch_async(serialQueue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(concurrentQueue, ^{
NSLog(@"thread:%@開始執行任務%d",[NSThread currentThread],(int)i);
sleep(1);
NSLog(@"thread:%@結束執行任務%d",[NSThread currentThread],(int)i);
dispatch_semaphore_signal(semaphore);});
});
}
/**
* 線程安全:使用 semaphore 加鎖
* 初始化火車票數量、賣票窗口(線程安全)、并開始賣票
*/
- (void)initTicketStatusSave {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"semaphore---begin");
semaphoreLock = dispatch_semaphore_create(1);
self.ticketSurplusCount = 50;
// queue1 代表北京火車票售賣窗口
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
// queue2 代表上海火車票售賣窗口
dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf saleTicketSafe];
});
dispatch_async(queue2, ^{
[weakSelf saleTicketSafe];
});
}
/**
* 售賣火車票(線程安全)
*/
- (void)saleTicketSafe {
while (1) {
// 相當于加鎖
dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
if (self.ticketSurplusCount > 0) { //如果還有票,繼續售賣
self.ticketSurplusCount--;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票數:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
[NSThread sleepForTimeInterval:0.2];
} else { //如果已賣完,關閉售票窗口
NSLog(@"所有火車票均已售完");
// 相當于解鎖
dispatch_semaphore_signal(semaphoreLock);
break;
}
// 相當于解鎖
dispatch_semaphore_signal(semaphoreLock);
}
}
dispatch_set_target_queue
作用:變更生成的dispatch queue的優先級
dispatch_apply
快速迭代遍歷,多線程進行。
下面代碼end輸出是在dispatch_apply完成之后。
dispatch_queue_t queue =dispatch_queue_create("apply并行隊列", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(count, queue, ^(size_t index) {
NSLog(@"%@----%@",array[index],[NSThread currentThread]);
});
NSLog(@"end");
四、GCD執行任務的順序
場景一
- (void)examine {
NSLog(@"1"); // 任務1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任務2
});
NSLog(@"3"); // 任務3
}
// log
1
/*
* 主線程執行 examine 方法,相當于把 examine 放到了主線程的隊列當中
* 接下來遇到了同步執行,相當于把任務 2 放到了主線程的隊列當中
* 此時的情況是,任務 2 會等待 examine 執行完成,同時,由于是同步執行,examine 也會等待 任務 2 執行完成,形成死鎖
* 也可以這樣說,examine 函數中的任務 3 會等待 任務 2 執行完成,任務 2 會等待 examine 中的任務 3 執行完成,形成死鎖
* 導致程序崩潰
*/
場景二
- (void)examine {
NSLog(@"1"); // 任務1
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"2"); // 任務2
});
NSLog(@"3"); // 任務3
}
// log:
1
2
3
/*
* 主線程執行 examine 方法,相當于把 examine 放到了主線程的隊列當中
* 接下來遇到同步任務,相當于把任務 2 放到了子隊列當中執行
* 任務 2 執行完成之后,回到主線程,繼續執行任務 3
* 不會造成死鎖
*/
場景三
- (void)examine {
dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); // 任務1
dispatch_async(queue, ^{
NSLog(@"2"); // 任務2
dispatch_sync(queue, ^{
NSLog(@"3"); // 任務3
});
NSLog(@"4"); // 任務4
});
NSLog(@"5"); // 任務5
}
// log:
1
5
2
// 5和2順序不一定
/*
* 執行任務 1,接下來遇到串行隊列異步執行,不會阻塞線程,接下來有可能執行任務 5
* 在異步執行的過程中,先執行任務 2,接下來遇到同步執行,并且將同步任務 3 放到當前串行隊列當中
* 所以任務 3 會等待此串行隊列中的任務 4 執行完畢
* 由于次串行隊列中遇到同步任務 3,他就會等待任務 3 執行完成之后再執行任務 4,形成死鎖
* 造成程序崩潰
*/
場景四
- (void)examine {
NSLog(@"1"); // 任務1
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2"); // 任務2
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3"); // 任務3
});
NSLog(@"4"); // 任務4
});
NSLog(@"5"); // 任務5
}
// log:
1
2
5
3
4
// 5和2順序不定
/*
* 首先,將【任務1、異步線程、任務5】加入Main Queue中,異步線程中的任務是:【任務2、同步線程、任務4】。
* 所以,先執行任務1,然后將異步線程中的任務加入到Global Queue中,因為異步線程,所以任務5不用等待,結果就是2和5的輸出順序不一定。
* 然后再看異步線程中的任務執行順序。任務2執行完以后,遇到同步線程。將同步線程中的任務加入到Main Queue中,這時加入的任務3在任務5的后面。
* 當任務3執行完以后,沒有了阻塞,程序繼續執行任務4。
* 從以上的分析來看,得到的幾個結果:1最先執行;2和5順序不一定;4一定在3后面。
*/
場景五
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1"); // 任務1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任務2
});
NSLog(@"3"); // 任務3
});
NSLog(@"4"); // 任務4
while (1) {
}
NSLog(@"5"); // 任務5
// log:
1
4
// 1和4順序不定
/*
* 和上面幾個案例的分析類似,先來看看都有哪些任務加入了Main Queue:【異步線程、任務4、死循環、任務5】。
* 在加入到Global Queue異步線程中的任務有:【任務1、同步線程、任務3】。
* 第一個就是異步線程,任務4不用等待,所以結果任務1和任務4順序不一定。
* 任務4完成后,程序進入死循環,Main Queue阻塞。但是加入到Global Queue的異步線程不受影響,繼續執行任務1后面的同步線程。
* 同步線程中,將任務2加入到了主線程,并且,任務3等待任務2完成以后才能執行。
* 這時的主線程,已經被死循環阻塞了。所以任務2無法執行,當然任務3也無法執行,在死循環后的任務5也不會執行。
* 最終,只能得到1和4順序不定的結果。
*/
場景六
- (void)examine {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"A");
});
NSLog(@"B");
dispatch_queue_t queueTemp = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0ul);
dispatch_async(queueTemp, ^{
NSLog(@"C");
});
dispatch_async(queueTemp, ^{
NSLog(@"D");
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"E");
});
/*
* 此方法為異步執行,即使 afterDelay 為 0,仍為異步
* method 方法會在主線程中執行
* 所以,此方法相當于 主線程中的異步任務
*/
[self performSelector:@selector(method) withObject:nil afterDelay:0.0];
NSLog(@"F");
}
/*
* 執行順序為 B C D F A E G
* 其中 C 和 D 的順序不定
* 并且 C 和 D 可能在 B 和 G 之間的任一個時刻執行
*/