在講多線程之前,先來熟悉幾個概念。
1.串行(Serial)與并行(Concurrent)
串行時一次只能執行一個任務,并行時多個任務在同時執行。此處的任務可以理解為object-c的block。
2.同步(Synchronous)與異步(Asynchronous)
簡單來說:同步會阻塞當前線程,異步不會,異步重現開一個線程,將任務丟到新開的線程中去執行。
3.線程安全
一個類或者程序所提供的接口對于線程來說是原子操作或者多個線程之間的切換不會導致該接口的執行結果存在二義性,就說線程安全的。反之,結果存在二義性,就是非線程安全的。
線程安全問題多半是由全局變量和靜態變量引起的。一般用互斥鎖解決線程不安全的問題。
@synchronized(鎖對象) {
// 將線程不安全的代碼寫在這里
}
4.上下文切換-Context Switch
一個上下文切換指當你在單個進程里切換執行不同的線程時存儲與恢復執行狀態的過程
5.任務
任務即操作,其實就是一段具體做某件事情的代碼,在GCD中就是一個block,任務有兩種執行方式:同步執行和異步執行
*同步執行(sync):會阻塞當前線程并等待block中的任務執行完畢,然后當前線程才會繼續執行下去;
*當前線程會繼續往下執行,不會阻塞當前線程>
6.隊列-Queues
隊列是一種遵循先進先出FIFO的線性表。隊列用于存放任務,GCD提供dispatch queues采用FIFO的順序來處理這些代碼塊。多有的調度隊列自身都是現場安全的。隊列有分為串行隊列和并行隊列
。
*串行隊列:串行隊列中的任務GCD會根據FIFO(先進先出)的原則,取出一個,執行一個。
*并行隊列:并行隊列中的任務GCD依然使用FIFO原則,不同的是,GCD把取出來的任務放到一個個不同的線程中去執行,由于速度很快,所以看起來所有的任務都是一起執行的。也就是同一時間內可以有多個任務被執行。`
| 同步執行 | 異步執行 |
---------| ------------- |------
串行隊列 | 當前線程,一個一個執行 |其它線程,一個一個執行
并行隊列 | 當前線程,一個一個執行 | 開多個線程,一起執行
進程線程和任務之間的關系
可以看到一個進程process可以包含多個線程thread,每個線程又可以同時執行多個任務tasks。
ios中的多線程方案有Pthread,NSThread,GCD,NSOperation&NSOperationQueue
1.Pthread
據說沒什么卵用,那就不管吧,??
2.NSThread
3.NSOperation
NSOperation是對GCD的封裝,是面向對象的,使用時直接控制線程對象即可,比較方便,但是NSOperation的生命周期需要手動管理。相比GCD,NSOperation更易于操作一個線程的暫停,取消或掛起。
NSOperation只是一個抽象的類,因此不能封裝任務,具體的執行由它的兩個子類
操作:
1.將要執行的任務封裝到NSOperation對象中;
2.將該任務添加到NSOperationQueue對象中;
4.GCD
Grand Central Dispatch,可以將應用程序需要執行的工作拆分成多個可以分散到多個線程中的小塊。GCD能自動管理線程的生命周期(創建線程,任務調度,銷毀線程),使用時只需要把要做的工作添加進去就可以了,但是如果你想讓該線程暫停,掛起或者取消它的運行就要需要增加更多的工作了。
系統提供的dispatch方法
1.后臺執行
// 默認優先級,在高優先任務完成后,低優先級任務未開始前執行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
});
// 最低優先級,隊列中擁有最低優先級的任務會較默認優先級和最高優先級的任務之后執行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
});
// 最高優先級,表示隊列中擁有最高優先級的任務會較默認優先級和低優先級的任務之前執行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
});
2.主線程執行
dispatch_async(dispatch_get_main_queue(), ^{
});
3.一次執行,保證線程安全
最常見的用法就是單利了,當多個對象對單利同時進行初始化,讀或者寫是就容易導致線程的不安全。dispatch_once可以保證目標任務(代碼)只被執行一次。
+(mySingleton *)sharedInstance{
static mySingleton *single = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
single = [[mySingleton alloc] init]; // 保證single實例只被初始化一次
});
return single;
}
4.延遲執行
指定某個時間點后執行的隊列,有點類似定時器。將任務延遲到某個時刻再執行
double delaySeconds = 1.0;
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delaySeconds * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
});
5.自定義的串行或者并行隊列。
// 自定義串行隊列
dispatch_queue_create("elena1", DISPATCH_QUEUE_SERIAL); // 參數“elena1”是一個標識符,用于debug的時候標識唯一的隊列,可以為空,DISPATCH_QUEUE_SERIAL 或 NULL 表示創建串行隊列
// 自定義并行隊列
dispatch_queue_create("elena2", DISPATCH_QUEUE_CONCURRENT);// 標識符"elena2",DISPATCH_QUEUE_CONCURRENT表明這是一個并行隊列
6.一些高級用法
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 并行執行的線程A
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// 并行執行的線程B
});
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 線程A和線程B完成后,匯總結果
});
具體使用
情景舉例:UserLocationController是一個專門用于定位及與位置相關操作的控制器,它有兩個代理方法,定位成功及定位失敗。
// 定位成功
-(void)locationSuccess:(NSDictionary *)dic{
[self useLocationToGetData:dic];// 定位成功
}
// 定位失敗
-(void)locationFailure:(NSError *)error{
[self actionFailure:error];
}
/**使用定位的控制器***/
- (void)viewDidLoad
{
[super viewDidLoad];
[self function1];
[self location];
[self function2];
}
-(void)function1{
NSLog(@"%s",__func__);
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
btn.frame = CGRectMake(100, 100, 70, 30);
[btn setBackgroundColor:RED];
[btn setTitle:@"click" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(btnClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
-(void)function2{
NSLog(@"%s",__func__);
}
-(void)btnClick{
NSLog(@"%s",__func__);
}
-(void)location{
NSLog(@"%s",__func__);
// 方式一
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UserLocationController *locationVC = [[UserLocationController alloc] initWithLocateToCurrentAdd:YES];
locationVC.view.frame = self.view.bounds;
NSLog(@"開始定位");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"定位完成,更新UI");
locationVC.delegate=self;
});
});
/*結果:function1,location和function2函數被依次執行,location被添加到global_queue線程中執行,定位完成后更新UI。在定位的過程中,按鈕依然可以點擊,整個過程不會因為定位而導致程序卡主不動*/
// 方式二
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL); // 同步串行隊列
dispatch_sync(serialQueue, ^{
UserLocationController *locationVC = [[UserLocationController alloc] initWithLocateToCurrentAdd:YES];
locationVC.view.frame = self.view.bounds;
locationVC.delegate = self;
NSLog(@"定位完成");
});
/*結果:function1,location和function2函數被依次執行,定位操作被放入到自定義的串行隊列中,并且是同步執行的,因此在定位完成前按鈕不可以點擊,整個過程因為定位而導致程序卡主不動*/
// 方式三
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_CONCURRENT); // 并行串行隊列
dispatch_async(serialQueue, ^{
UserLocationController *locationVC = [[UserLocationController alloc] initWithLocateToCurrentAdd:YES];
locationVC.view.frame = self.view.bounds;
locationVC.delegate = self;
NSLog(@"location finish");
});
/*結果:定位操作被添加到一個自定義的異步并行隊列中處理,在定位完成前,按鈕可點擊,效果與方式一相同*/
// 方式四
double delaySeconds = 20.0;
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delaySeconds * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
UserLocationController *locationVC = [[UserLocationController alloc] initWithLocateToCurrentAdd:YES];
locationVC.view.frame = self.view.bounds;
locationVC.delegate = self;
NSLog(@"finish");
});
/*結果:定位操作被延遲到20秒后執行,而且是被添加到了主線程中,毫無疑問,按鈕是可以響應的。這個例子毫無用處,僅僅是個舉例,你肯定希望定位操作盡快友好的完成*/
}
相關閱讀:
https://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1