前言
前言:了解多線程之前首先了解一些計(jì)算機(jī)的基本概念
計(jì)算機(jī)操作系統(tǒng)的基本概念
進(jìn)程: 一個(gè)具有一定獨(dú)立功能的程序關(guān)于某個(gè)數(shù)據(jù)集合的一次運(yùn)行活動(dòng)扣泊〗叮可以理解成一個(gè)運(yùn)行中的應(yīng)用程序。
線程: 程序執(zhí)行流的最小單元旷赖,線程是進(jìn)程中的一個(gè)實(shí)體顺又。
同步: 只能在當(dāng)前線程按先后順序依次執(zhí)行更卒,不開啟新線程等孵。
異步: 可以在當(dāng)前線程開啟多個(gè)新線程執(zhí)行,可不按順序執(zhí)行蹂空。
隊(duì)列: 裝載線程任務(wù)的隊(duì)形結(jié)構(gòu)俯萌。
并發(fā): 線程執(zhí)行可以同時(shí)一起進(jìn)行執(zhí)行。
串行: 線程執(zhí)行只能依次逐一先后有序的執(zhí)行上枕。
1.什么是多線程
多線程是一個(gè)比較輕量級(jí)的方法來實(shí)現(xiàn)單個(gè)應(yīng)用程序內(nèi)多個(gè)代碼執(zhí)行路徑咐熙。在系 統(tǒng)級(jí)別內(nèi),程序并排執(zhí)行辨萍,系統(tǒng)分配到每個(gè)程序的執(zhí)行時(shí)間是基于該程序的所需時(shí)間 和其他程序的所需時(shí)間來決定的棋恼。然而在每個(gè)應(yīng)程序的內(nèi)部,存在一個(gè)或多個(gè)執(zhí)行線 程锈玉,它同時(shí)或在一個(gè)幾乎同時(shí)發(fā)生的方式里執(zhí)行不同的任務(wù)爪飘。系統(tǒng)本身管理這些執(zhí)行 的線程,調(diào)度它們?cè)诳捎玫膬?nèi)核上運(yùn)行拉背,并在需要讓其他線程執(zhí)行的時(shí)候搶先打斷它 們师崎。
從技術(shù)角度來看,一個(gè)線程就是一個(gè)需要管理執(zhí)行代碼的內(nèi)核級(jí)和應(yīng)用級(jí)數(shù)據(jù)結(jié) 構(gòu)組合椅棺。內(nèi)核級(jí)結(jié)構(gòu)協(xié)助調(diào)度線程事件犁罩,并搶占式調(diào)度一個(gè)線程到可用的內(nèi)核之上。 應(yīng)用級(jí)結(jié)構(gòu)包括用于存儲(chǔ)函數(shù)調(diào)用的調(diào)用堆棧和應(yīng)用程序需要管理和操作線程屬性 和狀態(tài)的結(jié)構(gòu)两疚。
摘取自<多線程編程指南>
2.iOS多線程對(duì)比
1.NSThread每個(gè)NSThread對(duì)象對(duì)應(yīng)一個(gè)線程床估,真正最原始的線程。
1)優(yōu)點(diǎn):NSThread 輕量級(jí)最低诱渤,相對(duì)簡單顷窒。
2)缺點(diǎn):手動(dòng)管理所有的線程活動(dòng),如生命周期、線程同步鞋吉、睡眠等鸦做。
2.1.1NSThread三種實(shí)現(xiàn)開啟線程方式
1.動(dòng)態(tài)實(shí)例化
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(loadImageSource:) object:imgUrl];
thread.threadPriority = 1;// 設(shè)置線程的優(yōu)先級(jí)(0.0 - 1.0,1.0最高級(jí))
[thread start];
2.靜態(tài)實(shí)例化
[NSThread detachNewThreadSelector:@selector(loadImageSource:) toTarget:self withObject:imgUrl];
3.隱式實(shí)例化
[self performSelectorInBackground:@selector(loadImageSource:) withObject:imgUrl];
//在指定線程上操作
[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES];
2.1.2使用場(chǎng)景1(開啟子線程加載一張圖片)
解決線程阻塞問題在資源下載過程中谓着,由于網(wǎng)絡(luò)原因有時(shí)候很難保證下載時(shí)間泼诱,如果不使用多線程可能用戶完成一個(gè)下載操作需要長時(shí)間的等待,這個(gè)過程中無法進(jìn)行其他操作赊锚。下面演示一個(gè)采用多線程下載圖片的過程治筒,在這個(gè)示例中點(diǎn)擊按鈕會(huì)啟動(dòng)一個(gè)線程去下載圖片,下載完成后使用UIImageView將圖片顯示到界面中舷蒲∷释啵可以看到用戶點(diǎn)擊完下載按鈕后,不管圖片是否下載完成都可以繼續(xù)操作界面牲平,不會(huì)造成阻塞堤框。
@interface NSThreadViewController (){
UIImageView *_imageView;
}
@end
@implementation NSThreadViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor];
[self layoutUI];
}
#pragma mark 界面布局
-(void)layoutUI{
_imageView =[[UIImageView alloc]initWithFrame:[UIScreen mainScreen].bounds];
_imageView.contentMode=UIViewContentModeScaleAspectFit;
[self.view addSubview:_imageView];
UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame=CGRectMake(50, 500, 220, 25);
[button setTitle:@"加載圖片" forState:UIControlStateNormal];
//添加方法
[button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
#pragma mark 將圖片顯示到界面
-(void)updateImage:(NSData *)imageData{
UIImage *image=[UIImage imageWithData:imageData];
_imageView.image=image;
}
#pragma mark 請(qǐng)求圖片數(shù)據(jù)
-(NSData *)requestData{
//對(duì)于多線程操作建議把線程操作放到@autoreleasepool中
@autoreleasepool {
NSURL *url=[NSURL URLWithString:@"https://store.storeimages.cdn-apple.com/8750/as-images.apple.com/is/image/AppleInc/aos/published/images/s/eg/segment/hero/segment-hero-macbook-2017_GEO_CN?wid=400&hei=300&fmt=png-alpha&qlt=95&.v=1501280548817"];
NSData *data=[NSData dataWithContentsOfURL:url];
return data;
}
}
#pragma mark 加載圖片
-(void)loadImage{
//請(qǐng)求數(shù)據(jù)
NSData *data= [self requestData];
/*將數(shù)據(jù)顯示到UI控件,注意只能在主線程中更新UI,
另外performSelectorOnMainThread方法是NSObject的分類方法,每個(gè)NSObject對(duì)象都有此方法纵柿,
它調(diào)用的selector方法是當(dāng)前調(diào)用控件的方法蜈抓,例如使用UIImageView調(diào)用的時(shí)候selector就是UIImageView的方法
Object:代表調(diào)用方法的參數(shù),不過只能傳遞一個(gè)參數(shù)(如果有多個(gè)參數(shù)請(qǐng)使用對(duì)象進(jìn)行封裝)
waitUntilDone:是否線程任務(wù)完成執(zhí)行
*/
[self performSelectorOnMainThread:@selector(updateImage:) withObject:data waitUntilDone:YES];
}
#pragma mark 多線程下載圖片
-(void)loadImageWithMultiThread{
//方法1:使用對(duì)象方法
//創(chuàng)建一個(gè)線程,第一個(gè)參數(shù)是請(qǐng)求的操作昂儒,第二個(gè)參數(shù)是操作方法的參數(shù)
// NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage) object:nil];
// //啟動(dòng)一個(gè)線程沟使,注意啟動(dòng)一個(gè)線程并非就一定立即執(zhí)行,而是處于就緒狀態(tài)渊跋,當(dāng)系統(tǒng)調(diào)度時(shí)才真正執(zhí)行
// [thread start];
//方法2:使用類方法
[NSThread detachNewThreadSelector:@selector(loadImage) toTarget:self withObject:nil];
}
2.1.3使用場(chǎng)景2(開啟子線程加載多張圖片)
#import "NSThreadTwoViewController.h"
#import "KCImageData.h"
#define ROW_COUNT 5
#define COLUMN_COUNT 3
#define ROW_HEIGHT 100
#define ROW_WIDTH ROW_HEIGHT
#define CELL_SPACING 10
@interface NSThreadTwoViewController (){
NSMutableArray *_imageViews;
}
@end
@implementation NSThreadTwoViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor];
[self layoutUI];
}
#pragma mark 界面布局
-(void)layoutUI{
//創(chuàng)建多個(gè)圖片控件用于顯示圖片
_imageViews=[NSMutableArray array];
for (int r=0; r<ROW_COUNT; r++) {
for (int c=0; c<COLUMN_COUNT; c++) {
UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), 100+r*ROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)];
imageView.contentMode=UIViewContentModeScaleAspectFit;
// imageView.backgroundColor=[UIColor redColor];
[self.view addSubview:imageView];
[_imageViews addObject:imageView];
}
}
UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame=CGRectMake(50, 500, 220, 25);
[button setTitle:@"加載圖片" forState:UIControlStateNormal];
//添加方法
[button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
#pragma mark 將圖片顯示到界面
-(void)updateImage:(KCImageData *)imageData{
UIImage *image=[UIImage imageWithData:imageData.data];
UIImageView *imageView= _imageViews[imageData.index];
imageView.image=image;
}
#pragma mark 請(qǐng)求圖片數(shù)據(jù)
-(NSData *)requestData:(NSInteger )index{
//對(duì)于多線程操作建議把線程操作放到@autoreleasepool中
@autoreleasepool {
NSURL *url=[NSURL URLWithString:@"https://store.storeimages.cdn-apple.com/8750/as-images.apple.com/is/image/AppleInc/aos/published/images/s/eg/segment/hero/segment-hero-macbook-2017_GEO_CN?wid=400&hei=300&fmt=png-alpha&qlt=95&.v=1501280548817"];
NSData *data=[NSData dataWithContentsOfURL:url];
return data;
}
}
#pragma mark 加載圖片
-(void)loadImage:(NSNumber *)index{
// NSLog(@"%i",i);
//currentThread方法可以取得當(dāng)前操作線程
NSLog(@"current thread:%@",[NSThread currentThread]);
NSInteger i=[index integerValue];
// NSLog(@"%i",i);//未必按順序輸出
NSData *data= [self requestData:i];
KCImageData *imageData=[[KCImageData alloc]init];
imageData.index= i;
imageData.data=data;
[self performSelectorOnMainThread:@selector(updateImage:) withObject:imageData waitUntilDone:YES];
}
#pragma mark 多線程下載圖片
-(void)loadImageWithMultiThread{
//創(chuàng)建多個(gè)線程用于填充圖片
for (int i=0; i<ROW_COUNT*COLUMN_COUNT; ++i) {
// [NSThread detachNewThreadSelector:@selector(loadImage:) toTarget:self withObject:[NSNumber numberWithInt:i]];
NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage:) object:[NSNumber numberWithInt:i]];
thread.name=[NSString stringWithFormat:@"myThread%i",i];//設(shè)置線程名稱
[thread start];
}
}
從上面的運(yùn)行效果大家不難發(fā)現(xiàn)腊嗡,圖片并未按順序加載,原因有兩個(gè):第一拾酝,每個(gè)線程的實(shí)際執(zhí)行順序并不一定按順序執(zhí)行(雖然是按順序啟動(dòng))燕少;第二,每個(gè)線程執(zhí)行時(shí)實(shí)際網(wǎng)絡(luò)狀況很可能不一致微宝。當(dāng)然網(wǎng)絡(luò)問題無法改變棺亭,只能盡可能讓網(wǎng)速更快,但是可以改變線程的優(yōu)先級(jí)蟋软,讓15個(gè)線程優(yōu)先執(zhí)行某個(gè)線程镶摘。線程優(yōu)先級(jí)范圍為0~1,值越大優(yōu)先級(jí)越高岳守,每個(gè)線程的優(yōu)先級(jí)默認(rèn)為0.5凄敢。修改圖片下載方法如下,改變最后一張圖片加載的優(yōu)先級(jí)湿痢,這樣可以提高它被優(yōu)先加載的幾率涝缝,但是它也未必就第一個(gè)加載扑庞。因?yàn)槭紫绕渌€程是先啟動(dòng)的,其次網(wǎng)絡(luò)狀況我們沒辦法修改拒逮。
線程狀態(tài)
線程狀態(tài)分為isExecuting(正在執(zhí)行)罐氨、isFinished(已經(jīng)完成)、isCancellled(已經(jīng)取消)三種滩援。其中取消狀態(tài)程序可以干預(yù)設(shè)置栅隐,只要調(diào)用線程的cancel方法即可。但是需要注意在主線程中僅僅能設(shè)置線程狀態(tài)玩徊,并不能真正停止當(dāng)前線程租悄,如果要終止線程必須在線程中調(diào)用exist方法,這是一個(gè)靜態(tài)方法恩袱,調(diào)用該方法可以退出當(dāng)前線程泣棋。
假設(shè)在圖片加載過程中點(diǎn)擊停止按鈕讓沒有完成的線程停止加載,具體實(shí)現(xiàn)如下:
#import "NSthreadThreeViewController.h"
#import "KCImageData.h"
#define ROW_COUNT 5
#define COLUMN_COUNT 3
#define ROW_HEIGHT 100
#define ROW_WIDTH ROW_HEIGHT
#define CELL_SPACING 10
#define IMAGE_COUNT 9
@interface NSthreadThreeViewController (){
NSMutableArray *_imageViews;
NSMutableArray *_imageNames;
NSMutableArray *_threads;
}
@end
@implementation NSthreadThreeViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor];
[self layoutUI];
}
#pragma mark 界面布局
-(void)layoutUI{
//創(chuàng)建多個(gè)圖片空間用于顯示圖片
_imageViews=[NSMutableArray array];
for (int r=0; r<ROW_COUNT; r++) {
for (int c=0; c<COLUMN_COUNT; c++) {
UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), 100+r*ROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)];
imageView.contentMode=UIViewContentModeScaleAspectFit;
// imageView.backgroundColor=[UIColor redColor];
[self.view addSubview:imageView];
[_imageViews addObject:imageView];
}
}
//加載按鈕
UIButton *buttonStart=[UIButton buttonWithType:UIButtonTypeRoundedRect];
buttonStart.frame=CGRectMake(50, 500, 100, 25);
[buttonStart setTitle:@"加載圖片" forState:UIControlStateNormal];
[buttonStart addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:buttonStart];
//停止按鈕
UIButton *buttonStop=[UIButton buttonWithType:UIButtonTypeRoundedRect];
buttonStop.frame=CGRectMake(160, 500, 100, 25);
[buttonStop setTitle:@"停止加載" forState:UIControlStateNormal];
[buttonStop addTarget:self action:@selector(stopLoadImage) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:buttonStop];
//創(chuàng)建圖片鏈接
_imageNames=[NSMutableArray array];
for (int i=0; i<ROW_COUNT*COLUMN_COUNT; i++) {
[_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]];
}
}
#pragma mark 將圖片顯示到界面
-(void)updateImage:(KCImageData *)imageData{
UIImage *image=[UIImage imageWithData:imageData.data];
UIImageView *imageView= _imageViews[imageData.index];
imageView.image=image;
}
#pragma mark 請(qǐng)求圖片數(shù)據(jù)
-(NSData *)requestData:(NSInteger )index{
//對(duì)于多線程操作建議把線程操作放到@autoreleasepool中
@autoreleasepool {
NSURL *url=[NSURL URLWithString:_imageNames[index]];
NSData *data=[NSData dataWithContentsOfURL:url];
return data;
}
}
#pragma mark 加載圖片
-(void)loadImage:(NSNumber *)index{
NSInteger i=[index integerValue];
NSData *data= [self requestData:i];
NSThread *currentThread=[NSThread currentThread];
// 如果當(dāng)前線程處于取消狀態(tài)畔塔,則退出當(dāng)前線程
if (currentThread.isCancelled) {
NSLog(@"thread(%@) will be cancelled!",currentThread);
[NSThread exit];//取消當(dāng)前線程
}
KCImageData *imageData=[[KCImageData alloc]init];
imageData.index=i;
imageData.data=data;
[self performSelectorOnMainThread:@selector(updateImage:) withObject:imageData waitUntilDone:YES];
}
#pragma mark 多線程下載圖片
-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT;
_threads=[NSMutableArray arrayWithCapacity:count];
//創(chuàng)建多個(gè)線程用于填充圖片
for (int i=0; i<count; ++i) {
NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage:) object:[NSNumber numberWithInt:i]];
thread.name=[NSString stringWithFormat:@"myThread%i",i];//設(shè)置線程名稱
[_threads addObject:thread];
}
//循環(huán)啟動(dòng)線程
for (int i=0; i<count; ++i) {
NSThread *thread= _threads[i];
[thread start];
}
}
#pragma mark 停止加載圖片
-(void)stopLoadImage{
for (int i=0; i<ROW_COUNT*COLUMN_COUNT; i++) {
NSThread *thread= _threads[i];
//判斷線程是否完成潭辈,如果沒有完成則設(shè)置為取消狀態(tài)
//注意設(shè)置為取消狀態(tài)僅僅是改變了線程狀態(tài)而言,并不能終止線程
if (!thread.isFinished) {
[thread cancel];
}
}
}
實(shí)現(xiàn)效果如圖
2.NSOperation
自帶線程管理的抽象類俩檬。
1)優(yōu)點(diǎn):自帶線程周期管理萎胰,操作上可更注重自己邏輯碾盟。
2)缺點(diǎn):面向?qū)ο蟮某橄箢惻锪桑荒軐?shí)現(xiàn)它或者使用它定義好的兩個(gè)子類:NSInvocationOperation 和 NSBlockOperation。
主要的實(shí)現(xiàn)方式:結(jié)合NSOperation和NSOperationQueue實(shí)現(xiàn)多線程編程冰肴。
實(shí)例化NSOperation的子類屈藐,綁定執(zhí)行的操作。
創(chuàng)建NSOperationQueue隊(duì)列熙尉,將NSOperation實(shí)例添加進(jìn)來联逻。
系統(tǒng)會(huì)自動(dòng)將NSOperationQueue隊(duì)列中檢測(cè)取出和執(zhí)行NSOperation的操作。
①.NSInvocationOperation創(chuàng)建線程检痰。
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadImageSource:) object:imgUrl];
//[invocationOperation start];//直接會(huì)在當(dāng)前線程主線程執(zhí)行
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:invocationOperation];
②.NSBlockOperation創(chuàng)建線程
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
[self loadImageSource:imgUrl];
}];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:blockOperation];
2.2 NSInvocationOperation使用場(chǎng)景
首先使用NSInvocationOperation進(jìn)行一張圖片的加載演示包归,整個(gè)過程就是:創(chuàng)建一個(gè)操作,在這個(gè)操作中指定調(diào)用方法和參數(shù)铅歼,然后加入到操作隊(duì)列公壤。其他代碼基本不用修改,直接修加載圖片方法如下:
-(void)loadImageWithMultiThread{
/*創(chuàng)建一個(gè)調(diào)用操作
object:調(diào)用方法參數(shù)
*/
NSInvocationOperation *invocationOperation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(loadImage) object:nil];
//創(chuàng)建完NSInvocationOperation對(duì)象并不會(huì)調(diào)用椎椰,它由一個(gè)start方法啟動(dòng)操作厦幅,但是注意如果直接調(diào)用start方法,則此操作會(huì)在主線程中調(diào)用慨飘,一般不會(huì)這么操作,而是添加到NSOperationQueue中
// [invocationOperation start];
//創(chuàng)建操作隊(duì)列
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
//注意添加到操作隊(duì)后确憨,隊(duì)列會(huì)開啟一個(gè)線程執(zhí)行此操作
[operationQueue addOperation:invocationOperation];
}
2.2.2 NSBlockOperation使用場(chǎng)景:下面采用NSBlockOperation創(chuàng)建多個(gè)線程加載圖片。
#import "NSOperationViewController.h"
#import "KCImageData.h"
#define ROW_COUNT 5
#define COLUMN_COUNT 3
#define ROW_HEIGHT 100
#define ROW_WIDTH ROW_HEIGHT
#define CELL_SPACING 10
@interface NSOperationViewController (){
NSMutableArray *_imageViews;
NSMutableArray *_imageNames;
}
@end
@implementation NSOperationViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor];
[self layoutUI];
}
#pragma mark 界面布局
-(void)layoutUI{
//創(chuàng)建多個(gè)圖片控件用于顯示圖片
_imageViews=[NSMutableArray array];
for (int r=0; r<ROW_COUNT; r++) {
for (int c=0; c<COLUMN_COUNT; c++) {
UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), 100+r*ROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)];
imageView.contentMode=UIViewContentModeScaleAspectFit;
// imageView.backgroundColor=[UIColor redColor];
[self.view addSubview:imageView];
[_imageViews addObject:imageView];
}
}
UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame=CGRectMake(50, 500, 220, 25);
[button setTitle:@"加載圖片" forState:UIControlStateNormal];
//添加方法
[button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
//創(chuàng)建圖片鏈接
_imageNames=[NSMutableArray array];
for (int i=0; i<ROW_COUNT*COLUMN_COUNT; i++) {
[_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]];
}
}
#pragma mark 將圖片顯示到界面
-(void)updateImageWithData:(NSData *)data andIndex:(NSInteger)index{
UIImage *image=[UIImage imageWithData:data];
UIImageView *imageView= _imageViews[index];
imageView.image=image;
}
#pragma mark 請(qǐng)求圖片數(shù)據(jù)
-(NSData *)requestData:(NSInteger )index{
//對(duì)于多線程操作建議把線程操作放到@autoreleasepool中
@autoreleasepool {
NSURL *url=[NSURL URLWithString:_imageNames[index]];
NSData *data=[NSData dataWithContentsOfURL:url];
return data;
}
}
#pragma mark 加載圖片
-(void)loadImage:(NSNumber *)index{
NSInteger i=[index integerValue];
//請(qǐng)求數(shù)據(jù)
NSData *data= [self requestData:i];
NSLog(@"%@",[NSThread currentThread]);
//更新UI界面,此處調(diào)用了主線程隊(duì)列的方法(mainQueue是UI主線程)
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self updateImageWithData:data andIndex:i];
}];
}
#pragma mark 多線程下載圖片
//-(void)loadImageWithMultiThread{
// int count=ROW_COUNT*COLUMN_COUNT;
// //創(chuàng)建操作隊(duì)列
// NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
// operationQueue.maxConcurrentOperationCount=5;//設(shè)置最大并發(fā)線程數(shù)
// //創(chuàng)建多個(gè)線程用于填充圖片
// for (int i=0; i<count; ++i) {
// //方法1:創(chuàng)建操作塊添加到隊(duì)列
// // //創(chuàng)建多線程操作
// // NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
// // [self loadImage:[NSNumber numberWithInt:i]];
// // }];
// // //創(chuàng)建操作隊(duì)列
// //
// // [operationQueue addOperation:blockOperation];
//
// //方法2:直接使用操隊(duì)列添加操作
// [operationQueue addOperationWithBlock:^{
// [self loadImage:[NSNumber numberWithInt:i]];
// }];
//
// }
//}
-(void)loadImageWithMultiThread{//設(shè)置優(yōu)先加載最后一張照片
int count=ROW_COUNT*COLUMN_COUNT;
//創(chuàng)建操作隊(duì)列
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
operationQueue.maxConcurrentOperationCount=5;//設(shè)置最大并發(fā)線程數(shù)
NSBlockOperation *lastBlockOperation=[NSBlockOperation blockOperationWithBlock:^{
[self loadImage:[NSNumber numberWithInt:(count-1)]];
}];
//創(chuàng)建多個(gè)線程用于填充圖片
for (int i=0; i<count-1; ++i) {
//方法1:創(chuàng)建操作塊添加到隊(duì)列
//創(chuàng)建多線程操作
NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
[self loadImage:[NSNumber numberWithInt:i]];
}];
//設(shè)置依賴操作為最后一張圖片加載操作
[blockOperation addDependency:lastBlockOperation];
[operationQueue addOperation:blockOperation];
}
//將最后一個(gè)圖片的加載操作加入線程隊(duì)列
[operationQueue addOperation:lastBlockOperation];
}
線程執(zhí)行順序
前面使用NSThread很難控制線程的執(zhí)行順序,但是使用NSOperation就容易多了休弃,每個(gè)NSOperation可以設(shè)置依賴線程吞歼。假設(shè)操作A依賴于操作B,線程操作隊(duì)列在啟動(dòng)線程時(shí)就會(huì)首先執(zhí)行B操作塔猾,然后執(zhí)行A浆熔。對(duì)于前面優(yōu)先加載最后一張圖的需求,只要設(shè)置前面的線程操作的依賴線程為最后一個(gè)操作即可桥帆。修改圖片加載方法如下:
-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT;
//創(chuàng)建操作隊(duì)列
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
operationQueue.maxConcurrentOperationCount=5;//設(shè)置最大并發(fā)線程數(shù)
NSBlockOperation *lastBlockOperation=[NSBlockOperation blockOperationWithBlock:^{
[self loadImage:[NSNumber numberWithInt:(count-1)]];
}];
//創(chuàng)建多個(gè)線程用于填充圖片
for (int i=0; i<count-1; ++i) {
//方法1:創(chuàng)建操作塊添加到隊(duì)列
//創(chuàng)建多線程操作
NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
[self loadImage:[NSNumber numberWithInt:i]];
}];
//設(shè)置依賴操作為最后一張圖片加載操作
[blockOperation addDependency:lastBlockOperation];
[operationQueue addOperation:blockOperation];
}
//將最后一個(gè)圖片的加載操作加入線程隊(duì)列
[operationQueue addOperation:lastBlockOperation];
}
3.GCD
Grand Central Dispatch (GCD)是Apple開發(fā)的一個(gè)多核編程的解決方法医增。
1)優(yōu)點(diǎn):最高效,避開并發(fā)陷阱老虫。
2)缺點(diǎn):基于C實(shí)現(xiàn)叶骨。
通過 GCD,開發(fā)者不用再直接跟線程打交道了祈匙,只需要向隊(duì)列中添加代碼塊即可忽刽,GCD 在后端管理著一個(gè)線程池。GCD 不僅決定著你的代碼塊將在哪個(gè)線程被執(zhí)行夺欲,它還根據(jù)可用的系統(tǒng)資源對(duì)這些線程進(jìn)行管理跪帝。這樣可以將開發(fā)者從線程管理的工作中解放出來,通過集中的管理線程些阅,來緩解大量線程被創(chuàng)建的問題伞剑。
GCD 帶來的另一個(gè)重要改變是,作為開發(fā)者可以將工作考慮為一個(gè)隊(duì)列市埋,而不是一堆線程黎泣,這種并行的抽象模型更容易掌握和使用。
GCD 公開有 5 個(gè)不同的隊(duì)列:運(yùn)行在主線程中的 main queue缤谎,3 個(gè)不同優(yōu)先級(jí)的后臺(tái)隊(duì)列抒倚,以及一個(gè)優(yōu)先級(jí)更低的后臺(tái)隊(duì)列(用于 I/O)。 另外坷澡,開發(fā)者可以創(chuàng)建自定義隊(duì)列:串行或者并行隊(duì)列托呕。自定義隊(duì)列非常強(qiáng)大,在自定義隊(duì)列中被調(diào)度的所有 block 最終都將被放入到系統(tǒng)的全局隊(duì)列中和線程池中频敛。
Serial Diapatch Queue 串行隊(duì)列
當(dāng)任務(wù)相互依賴项郊,具有明顯的先后順序的時(shí)候,使用串行隊(duì)列是一個(gè)不錯(cuò)的選擇 創(chuàng)建一個(gè)串行隊(duì)列:
dispatch_queue_t serialDispatchQueue=dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_SERIAL);
第一個(gè)參數(shù)為隊(duì)列名姻政,第二個(gè)參數(shù)為隊(duì)列類型呆抑,當(dāng)然,第二個(gè)參數(shù)如果寫NULL汁展,創(chuàng)建出來的也是一個(gè)串行隊(duì)列鹊碍。然后我們?cè)诋惒骄€程來執(zhí)行這個(gè)隊(duì)列:
dispatch_async(serialDispatchQueue, ^{
NSLog(@"1");
});
dispatch_async(serialDispatchQueue, ^{
sleep(2);
NSLog(@"2");
});
dispatch_async(serialDispatchQueue, ^{
sleep(1);
NSLog(@"3");
});
為了能更好的理解厌殉,我給每個(gè)異步線程都添加了一個(gè)log,看一下日志平臺(tái)的log:
2016-03-07 10:17:13.907 GCD[2195:61497] 1
2016-03-07 10:17:15.911 GCD[2195:61497] 2
2016-03-07 10:17:16.912 GCD[2195:61497] 3
串行隊(duì)列使用場(chǎng)景
使用串行隊(duì)列時(shí)首先要?jiǎng)?chuàng)建一個(gè)串行隊(duì)列侈咕,然后調(diào)用異步調(diào)用方法公罕,在此方法中傳入串行隊(duì)列和線程操作即可自動(dòng)執(zhí)行。下面使用線程隊(duì)列演示圖片的加載過程耀销,你會(huì)發(fā)現(xiàn)多張圖片會(huì)按順序加載楼眷,因?yàn)楫?dāng)前隊(duì)列中只有一個(gè)線程。
#import "GCDViewController.h"
#import "KCImageData.h"
#define ROW_COUNT 5
#define COLUMN_COUNT 3
#define ROW_HEIGHT 100
#define ROW_WIDTH ROW_HEIGHT
#define CELL_SPACING 10
@interface GCDViewController (){
NSMutableArray *_imageViews;
NSMutableArray *_imageNames;
}
@end
@implementation GCDViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
[self layoutUI];
}
#pragma mark 界面布局
-(void)layoutUI{
//創(chuàng)建多個(gè)圖片控件用于顯示圖片
_imageViews=[NSMutableArray array];
for (int r=0; r<ROW_COUNT; r++) {
for (int c=0; c<COLUMN_COUNT; c++) {
UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), 100+r*ROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)];
imageView.contentMode=UIViewContentModeScaleAspectFit;
// imageView.backgroundColor=[UIColor redColor];
[self.view addSubview:imageView];
[_imageViews addObject:imageView];
}
}
UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame=CGRectMake(50, 500, 220, 25);
[button setTitle:@"加載圖片" forState:UIControlStateNormal];
//添加方法
[button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
//創(chuàng)建圖片鏈接
_imageNames=[NSMutableArray array];
for (int i=0; i<ROW_COUNT*COLUMN_COUNT; i++) {
[_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]];
}
}
#pragma mark 將圖片顯示到界面
-(void)updateImageWithData:(NSData *)data andIndex:(NSInteger )index{
UIImage *image=[UIImage imageWithData:data];
UIImageView *imageView= _imageViews[index];
imageView.image=image;
}
#pragma mark 請(qǐng)求圖片數(shù)據(jù)
-(NSData *)requestData:(NSInteger )index{
NSURL *url=[NSURL URLWithString:_imageNames[index]];
NSData *data=[NSData dataWithContentsOfURL:url];
return data;
}
#pragma mark 加載圖片
-(void)loadImage:(NSNumber *)index{
//如果在串行隊(duì)列中會(huì)發(fā)現(xiàn)當(dāng)前線程打印變化完全一樣熊尉,因?yàn)樗麄冊(cè)谝粋€(gè)線程中
NSLog(@"thread is :%@",[NSThread currentThread]);
NSInteger i=[index integerValue];
//請(qǐng)求數(shù)據(jù)
NSData *data= [self requestData:i];
//更新UI界面,此處調(diào)用了GCD主線程隊(duì)列的方法
dispatch_queue_t mainQueue= dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
[self updateImageWithData:data andIndex:i];
});
}
#pragma mark 多線程下載圖片
-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT;
/*創(chuàng)建一個(gè)串行隊(duì)列
第一個(gè)參數(shù):隊(duì)列名稱
第二個(gè)參數(shù):隊(duì)列類型
*/
dispatch_queue_t serialQueue=dispatch_queue_create("myThreadQueue1", DISPATCH_QUEUE_SERIAL);//注意queue對(duì)象不是指針類型
//創(chuàng)建多個(gè)線程用于填充圖片
for (int i=0; i<count; ++i) {
//異步執(zhí)行隊(duì)列任務(wù) 任務(wù)按照順序執(zhí)行 并且會(huì)開啟子線程去執(zhí)行
dispatch_async(serialQueue, ^{
[self loadImage:[NSNumber numberWithInt:i]];
});
}
//非ARC環(huán)境請(qǐng)釋放
// dispatch_release(seriQueue);
}
Concurrent Diapatch Queue 并發(fā)隊(duì)列
與串行隊(duì)列剛好相反罐柳,他不會(huì)存在任務(wù)間的相互依賴。
創(chuàng)建一個(gè)并發(fā)隊(duì)列:
dispatch_queue_t concurrentDispatchQueue=dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
比較2個(gè)隊(duì)列的創(chuàng)建狰住,我們發(fā)現(xiàn)只有第二個(gè)參數(shù)從DISPATCH_QUEUE_SERIAL變成了對(duì)應(yīng)的DISPATCH_QUEUE_CONCURRENT张吉,其他完全一樣。
用同一段代碼催植,換一種隊(duì)列我們來比較一下效果:
dispatch_async(concurrentDispatchQueue, ^{
NSLog(@"1");
});
dispatch_async(concurrentDispatchQueue, ^{
sleep(2);
NSLog(@"2");
});
dispatch_async(concurrentDispatchQueue, ^{
sleep(1);
NSLog(@"3");
});
輸出的log:
2016-03-07 10:42:38.289 GCD[2260:72557] 1
2016-03-07 10:42:39.291 GCD[2260:72559] 3
2016-03-07 10:42:40.293 GCD[2260:72556] 2
結(jié)論:我們發(fā)現(xiàn)肮蛹,log的輸出在3個(gè)不同編號(hào)的線程中進(jìn)行,而且相互不依賴创南,不阻塞伦忠。并且任務(wù)的執(zhí)行順序是無序的。
修改串行隊(duì)列加載圖片部分的代碼
-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT;
/*取得全局隊(duì)列
第一個(gè)參數(shù):線程優(yōu)先級(jí)
第二個(gè)參數(shù):標(biāo)記參數(shù)稿辙,目前沒有用昆码,一般傳入0
*/
dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//創(chuàng)建多個(gè)線程用于填充圖片
for (int i=0; i<count; ++i) {
//異步執(zhí)行隊(duì)列任務(wù)
dispatch_async(globalQueue, ^{
[self loadImage:[NSNumber numberWithInt:i]];
});
}
}
運(yùn)行效果如下
Global Queue & Main Queue
這是系統(tǒng)為我們準(zhǔn)備的2個(gè)隊(duì)列:
Global Queue其實(shí)就是系統(tǒng)創(chuàng)建的Concurrent Diapatch Queue
Main Queue 其實(shí)就是系統(tǒng)創(chuàng)建的位于主線程的Serial Diapatch Queue
通常情況我們會(huì)把這2個(gè)隊(duì)列放在一起使用,也是我們最常用的開異步線程-執(zhí)行異步任務(wù)-回主線程的一種方式
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"異步線程");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"異步主線程");
});
});
通過上面的代碼我們發(fā)現(xiàn)了2個(gè)有意思的點(diǎn):
dispatch_get_global_queue存在優(yōu)先級(jí)邓深,沒錯(cuò)未桥,他一共有4個(gè)優(yōu)先級(jí):
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSLog(@"4");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
NSLog(@"3");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"2");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"1");
});
在指定優(yōu)先級(jí)之后笔刹,同一個(gè)隊(duì)列會(huì)按照這個(gè)優(yōu)先級(jí)執(zhí)行芥备,打印的順序?yàn)?、2舌菜、3萌壳、4,當(dāng)然這不是串行隊(duì)列日月,所以不存在絕對(duì)回調(diào)先后袱瓮。(設(shè)置優(yōu)先級(jí)就是說可以優(yōu)先執(zhí)行某個(gè)任務(wù)但是這個(gè)任務(wù)會(huì)不會(huì)比其他任務(wù)先執(zhí)行完不能確定)
異步主線程
在日常工作中,除了在其他線程返回主線程的時(shí)候需要用這個(gè)方法爱咬,還有一些時(shí)候我們?cè)谥骶€程中直接調(diào)用異步主線程尺借,這是利用dispatch_async的特性:block中的任務(wù)會(huì)放在主線程本次runloop之后返回。這樣精拟,有些存在先后順序的問題就可以得到解決了燎斩。
說完了隊(duì)列虱歪,我們?cè)僬f說GCD提供的一些操作隊(duì)列的方法
dispatch_set_target_queue
剛剛我們說了系統(tǒng)的Global Queue是可以指定優(yōu)先級(jí)的,那我們?nèi)绾谓o自己創(chuàng)建的隊(duì)列執(zhí)行優(yōu)先級(jí)呢栅表?這里我們就可以用到dispatch_set_target_queue這個(gè)方法:
dispatch_queue_t serialDispatchQueue=dispatch_queue_create("com.test.queue", NULL);
dispatch_queue_t dispatchgetglobalqueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_set_target_queue(serialDispatchQueue, dispatchgetglobalqueue);
dispatch_async(serialDispatchQueue, ^{
NSLog(@"我優(yōu)先級(jí)低笋鄙,先讓讓");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"我優(yōu)先級(jí)高,我先block");
});
我把自己創(chuàng)建的隊(duì)列塞到了系統(tǒng)提供的global_queue隊(duì)列中,我們可以理解為:我們自己創(chuàng)建的queue其實(shí)是位于global_queue中執(zhí)行,所以改變global_queue的優(yōu)先級(jí)怪瓶,也就改變了我們自己所創(chuàng)建的queue的優(yōu)先級(jí)萧落。所以我們常用這種方式來管理子隊(duì)列。
dispatch_after
這個(gè)是最常用的洗贰,用來延遲執(zhí)行的GCD方法找岖,因?yàn)樵谥骶€程中我們不能用sleep來延遲方法的調(diào)用,所以用它是最合適的敛滋,我們做一個(gè)簡單的例子:
NSLog(@"小破孩-波波1");
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(@"小破孩-波波2");
});
輸出的結(jié)果:
2016-03-07 11:25:06.019 GCD[2443:95722] 小破孩-波波1
2016-03-07 11:25:08.019 GCD[2443:95722] 小破孩-波波2
我們看到他就是在主線程宣增,就是剛好延遲了2秒,當(dāng)然矛缨,我說這個(gè)2秒并不是絕對(duì)的爹脾,為什么這么說?還記得我之前在介紹dispatch_async這個(gè)特性的時(shí)候提到的嗎箕昭?他的block中方法的執(zhí)行會(huì)放在主線程runloop之后灵妨,所以,如果此時(shí)runloop周期較長的時(shí)候落竹,可能會(huì)有一些時(shí)差產(chǎn)生泌霍。
dispatch_group
當(dāng)我們需要監(jiān)聽一個(gè)并發(fā)隊(duì)列中,所有任務(wù)都完成了述召,就可以用到這個(gè)group朱转,因?yàn)椴l(fā)隊(duì)列你并不知道哪一個(gè)是最后執(zhí)行的,所以以單獨(dú)一個(gè)任務(wù)是無法監(jiān)聽到這個(gè)點(diǎn)的积暖,如果把這些單任務(wù)都放到同一個(gè)group藤为,那么,我們就能通過dispatch_group_notify方法知道什么時(shí)候這些任務(wù)全部執(zhí)行完成了夺刑。
dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group=dispatch_group_create();
dispatch_group_async(group, queue, ^{NSLog(@"0");});
dispatch_group_async(group, queue, ^{NSLog(@"1");});
dispatch_group_async(group, queue, ^{NSLog(@"2");});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"down");});
在例子中缅疟,我把3個(gè)log分別放在并發(fā)隊(duì)列中,通過把這個(gè)并發(fā)隊(duì)列任務(wù)統(tǒng)一加入group中遍愿,group每次runloop的時(shí)候都會(huì)調(diào)用一個(gè)方法dispatch_group_wait(group, DISPATCH_TIME_NOW)存淫,用來檢查group中的任務(wù)是否已經(jīng)完成,如果已經(jīng)完成了沼填,那么會(huì)執(zhí)行dispatch_group_notify的block桅咆,輸出’down’看一下運(yùn)行結(jié)果:
2016-03-07 14:21:58.647 GCD[9424:156388] 2
2016-03-07 14:21:58.647 GCD[9424:156382] 0
2016-03-07 14:21:58.647 GCD[9424:156385] 1
2016-03-07 14:21:58.650 GCD[9424:156324] down
此方法的作用是在并發(fā)隊(duì)列中,完成在它之前提交到隊(duì)列中的任務(wù)后打斷坞笙,單獨(dú)執(zhí)行其block岩饼,并在執(zhí)行完成之后才能繼續(xù)執(zhí)行在他之后提交到隊(duì)列中的任務(wù):
dispatch_queue_t concurrentDispatchQueue=dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentDispatchQueue, ^{NSLog(@"0");});
dispatch_async(concurrentDispatchQueue, ^{NSLog(@"1");});
dispatch_async(concurrentDispatchQueue, ^{NSLog(@"2");});
dispatch_async(concurrentDispatchQueue, ^{NSLog(@"3");});
dispatch_barrier_async(concurrentDispatchQueue, ^{sleep(1); NSLog(@"4");});
dispatch_async(concurrentDispatchQueue, ^{NSLog(@"5");});
dispatch_async(concurrentDispatchQueue, ^{NSLog(@"6");});
dispatch_async(concurrentDispatchQueue, ^{NSLog(@"7");});
dispatch_async(concurrentDispatchQueue, ^{NSLog(@"8");});
輸出的結(jié)果為:
2016-03-07 14:45:32.410 GCD[10079:169655] 1
2016-03-07 14:45:32.410 GCD[10079:169658] 2
2016-03-07 14:45:32.410 GCD[10079:169656] 0
2016-03-07 14:45:32.410 GCD[10079:169661] 3
2016-03-07 14:45:33.414 GCD[10079:169661] 4
2016-03-07 14:45:33.415 GCD[10079:169661] 5
2016-03-07 14:45:33.415 GCD[10079:169658] 6
2016-03-07 14:45:33.415 GCD[10079:169655] 8
2016-03-07 14:45:33.415 GCD[10079:169662] 7
總結(jié):4之后的任務(wù)在我線程sleep之后才執(zhí)行刽脖,這其實(shí)就起到了一個(gè)線程鎖的作用,在多個(gè)線程同時(shí)操作一個(gè)對(duì)象的時(shí)候忌愚,讀可以放在并發(fā)進(jìn)行曲管,當(dāng)寫的時(shí)候,我們就可以用dispatch_barrier_async方法硕糊,效果杠杠的院水。
dispatch_sync
dispatch_sync 會(huì)在當(dāng)前線程執(zhí)行隊(duì)列,并且阻塞當(dāng)前線程中之后運(yùn)行的代碼简十,所以檬某,同步線程非常有可能導(dǎo)致死鎖現(xiàn)象,我們這邊就舉一個(gè)死鎖的例子螟蝙,直接在主線程調(diào)用以下代碼:
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"有沒有同步主線程?");
});
根據(jù)FIFO(先進(jìn)先出)的原則恢恼,block里面的代碼應(yīng)該在主線程此次runloop后執(zhí)行,但是由于他是同步隊(duì)列胰默,所有他之后的代碼會(huì)等待其執(zhí)行完成后才能繼續(xù)執(zhí)行场斑,2者相互等待,所以就出現(xiàn)了死鎖牵署。
我們?cè)倥e一個(gè)比較特殊的例子:
dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{sleep(1);NSLog(@"1");});
dispatch_sync(queue, ^{sleep(1);NSLog(@"2");});
dispatch_sync(queue, ^{sleep(1);NSLog(@"3");});
NSLog(@"4");
其打印結(jié)果為:
2016-03-07 17:15:48.124 GCD[14198:272683] 1
2016-03-07 17:15:49.125 GCD[14198:272683] 2
2016-03-07 17:15:50.126 GCD[14198:272683] 3
2016-03-07 17:15:50.126 GCD[14198:272683] 4
從線程編號(hào)中我們發(fā)現(xiàn)漏隐,同步方法沒有去開新的線程,而是在當(dāng)前線程中執(zhí)行隊(duì)列奴迅,會(huì)有人問青责,上文說dispatch_get_global_queue不是并發(fā)隊(duì)列,并發(fā)隊(duì)列不是應(yīng)該會(huì)在開啟多個(gè)線程嗎取具?這個(gè)前提是用異步方法脖隶。GCD其實(shí)是弱化了線程的管理,強(qiáng)化了隊(duì)列管理暇检,這使我們理解變得比較形象产阱。
dispatch_apply
這個(gè)方法用于無序查找,在一個(gè)數(shù)組中占哟,我們能開啟多個(gè)線程來查找所需要的值心墅,我這邊也舉個(gè)例子:
NSArray *array=[[NSArray alloc]initWithObjects:@"0",@"1",@"2",@"3",@"4",@"5",@"6", nil];
dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply([array count], queue, ^(size_t index) {
NSLog(@"%zu=%@",index,[array objectAtIndex:index]);
});
NSLog(@"阻塞");
輸出結(jié)果:
2016-03-07 17:36:50.726 GCD[14318:291701] 1=1
2016-03-07 17:36:50.726 GCD[14318:291705] 0=0
2016-03-07 17:36:50.726 GCD[14318:291783] 3=3
2016-03-07 17:36:50.726 GCD[14318:291782] 2=2
2016-03-07 17:36:50.726 GCD[14318:291784] 5=5
2016-03-07 17:36:50.726 GCD[14318:291627] 4=4
2016-03-07 17:36:50.726 GCD[14318:291785] 6=6
2016-03-07 17:36:50.727 GCD[14318:291627] 阻塞
通過輸出log,我們發(fā)現(xiàn)這個(gè)方法雖然會(huì)開啟多個(gè)線程來遍歷這個(gè)數(shù)組榨乎,但是在遍歷完成之前會(huì)阻塞主線程。
dispatch_suspend & dispatch_resume
隊(duì)列掛起和恢復(fù)瘫筐,這個(gè)沒什么好說的蜜暑,直接上代碼:
dispatch_queue_t concurrentDispatchQueue=dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentDispatchQueue, ^{serialDispatchQueue
for (int i=0; i<100; i++)
{
NSLog(@"%i",i);
if (i==50)
{
NSLog(@"-----------------------------------");
dispatch_suspend(concurrentDispatchQueue);
sleep(3);
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_resume(concurrentDispatchQueue);
});
}
}
});
我們甚至可以在不同的線程對(duì)這個(gè)隊(duì)列進(jìn)行掛起和恢復(fù),因?yàn)镚CD是對(duì)隊(duì)列的管理策肝。
Semaphore
我們可以通過設(shè)置信號(hào)量的大小肛捍,來解決并發(fā)過多導(dǎo)致資源吃緊的情況隐绵,以單核CPU做并發(fā)為例,一個(gè)CPU永遠(yuǎn)只能干一件事情拙毫,那如何同時(shí)處理多個(gè)事件呢依许,聰明的內(nèi)核工程師讓CPU干第一件事情,一定時(shí)間后停下來缀蹄,存取進(jìn)度峭跳,干第二件事情以此類推,所以如果開啟非常多的線程缺前,單核CPU會(huì)變得非常吃力蛀醉,即使多核CPU,核心數(shù)也是有限的衅码,所以合理分配線程拯刁,變得至關(guān)重要,那么如何發(fā)揮多核CPU的性能呢逝段?如果讓一個(gè)核心模擬傳很多線程垛玻,經(jīng)常干一半放下干另一件事情,那效率也會(huì)變低奶躯,所以我們要合理安排夭谤,將單一任務(wù)或者一組相關(guān)任務(wù)并發(fā)至全局隊(duì)列中運(yùn)算或者將多個(gè)不相關(guān)的任務(wù)或者關(guān)聯(lián)不緊密的任務(wù)并發(fā)至用戶隊(duì)列中運(yùn)算,所以用好信號(hào)量巫糙,合理分配CPU資源朗儒,程序也能得到優(yōu)化,當(dāng)日常使用中参淹,信號(hào)量也許我們只起到了一個(gè)計(jì)數(shù)的作用醉锄,真的有點(diǎn)大材小用。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);//為了讓一次輸出10個(gè)浙值,初始信號(hào)量為10
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i <100; i++)
{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//每進(jìn)來1次恳不,信號(hào)量-1;進(jìn)來10次后就一直hold住,直到信號(hào)量大于0开呐;
dispatch_async(queue, ^{
NSLog(@"%i",i);
sleep(2);
dispatch_semaphore_signal(semaphore);//由于這里只是log,所以處理速度非逞萄快,我就模擬2秒后信號(hào)量+1;
});
}
dispatch_once
這個(gè)函數(shù)一般是用來做一個(gè)真的單例筐付,也是非常常用的卵惦,在這里就舉一個(gè)單例的例子吧:
static SingletonTimer * instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[SingletonTimer alloc] init];
});
return instance;
參考資料如下:
http://pingguohe.net/2016/03/07/GCD-is-so-easy.html
iOS開發(fā)系列--并行開發(fā)其實(shí)很容易
http://blog.jobbole.com/69019/
https://segmentfault.com/a/1190000006612189
文章中對(duì)應(yīng)的demo鏈接