学习GCD

GCD(Grand Central Dispatch)介绍

GCD是苹果为开发者提供的系统级别的线程管理api,开发过程中专门对多核心性能做了优化,所以在dispatch queue中执行任务性能很高。

GCD概要

  • 和operation queue一样都是基于队列的并发编程API,底层使用的是线程池的技术,集中管理大家协同使用的线程池。
  • 系统提供五个不同的全局队列
    • main queue 主线程队列,也是UI更新的线程
    • 四个不同优先级的队列(High Priority Queue,Default Priority Queue,Low Priority Queue),以及一个优先级更低的后台队列Background Priority Queue(用于I/O)
    • 可创建自定义队列:串行或并列队列。自定义队列一般放在Default Priority Queue和Main Queue里。
    • 操作是在多线程上还是单线程主要是看队列的类型和执行方法,并行队列异步执行才能在多线程,并行队列同步执行就只会在主线程执行了

基本概念

  • 系统提供的两个标准队列
//全局队列,是一个并行队列,可以设置不同的优先级
dispatch_get_globle_queue
//主队列,主线程中的唯一队列,是一个串行队列
dispatch_get_main_queue
  • 自定义队列
//串行队列
dispatch_queue_create("com.fineway.serialqueue",DISPATCH_QUEUE_SERIAL);
//并行队列
dispatch_queue_create("com.fineway.coucurrent",DISPATCH_QUEUE_CONCURRENT);
  • 同步异步线程的创建
//同步线程
dispatch_sync(..., ^(block))
//异步线程
dispatch_async(..., ^(block))

队列(dispatch queue)

  • Serial:又叫private dispatch queues,同时只执行一个任务。Serial queue常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然各自是同步,但serial queue之间是并发执行。
  • Main dispatch queue:全局可用的serial queue,在应用程序主线程上执行任务。
  • Concurrent:又叫global dispatch queue,可以并发的执行多个任务,但执行完成顺序是随机的。系统提供四个全局并发队列,这四个队列有着对应的优先级,用户是不能够创建全局队列的,只能获取。
dispatch_queue_t queue;
queue = dispatch_get_globle_queue( DISPATCH_QUEUE_PEIORITY_HIGH,0);
  • user create queue:创建自己定义的队列,可以用dispatch_queue_create函数,函数有两个参数,第一个自定义的队列名,第二个参数是队列类型,默认NULL或者DISPATCH_QUEUE_SERIAL的是串行,参数为DISPATCH_QUEUE_CONCURRENT为并行队列。
dispatch_queue_t queue ;
queue = dispatch_queue_create("com.fineway.concurrent",
DISPATCH_QUEUEU_CONCURRENT);
  • 自定义队列的优先级:可以通过dipatch_queue_attr_make_with_qos_class或dispatch_set_target_queue方法设置队列的优先级
//dipatch_queue_attr_make_with_qos_class
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, -1);
dispatch_queue_t queue = dispatch_queue_create("com.fineway.serialqueue", attr);
//dispatch_set_target_queue
dispatch_queue_t queue = dispatch_queue_create("com.fineway.serialqueue",NULL); //需要设置优先级的queue
dispatch_queue_t referQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); //参考优先级
dispatch_set_target_queue(queue, referQueue); //设置queue和referQueue的优先级一样
  • dispatch_set_target_queue:可以设置优先级,也可以设置队列层级体系,比如让多个串行和并行队列在统一一个串行队列里串行执行,如下
dispatch_queue_t serialQueue = dispatch_queue_create("com.fineway.serialqueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t firstQueue = dispatch_queue_create("com.fineway.serialqueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t secondQueue = dispatch_queue_create("com.fineway.serialqueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_set_target_queue(firstQueue, serialQueue);
dispatch_set_target_queue(secondQueue, serialQueue);

  dispatch_async(firstQueue, ^{ 
  NSLog(@"1"); 
  [NSThread sleepForTimeInterval:3.f];
});
dispatch_async(secondQueue, ^{ 
  NSLog(@"2"); 
  [NSThread sleepForTimeInterval:2.f];
});
dispatch_async(secondQueue, ^{ 
  NSLog(@"3"); 
  [NSThread sleepForTimeInterval:1.f];
});

队列类型

队列类型默认是串行的,如果创建队列的时候参数是NULL,队列是串行的,队列参数是DISPATCH_QUEUE_CONCURRENT时,队列是并行的,可以同时执行多个block。

  • 何时使用何种队列类型
    • 主队列(顺序):队列中有任务完成需要更新UI时,dispatch_after在这种类型中使用。
    • 并发队列:用来执行与UI无关的后台任务,dispatch_sync放在这里,方便等待任务完成进行后续处理或和dispatch barrier同步。dispatch groups放在这里也不错。
    • 自定义顺序队列:顺序执行后台任务并追踪它时。这样做同时只有一个任务在执行可以防止资源竞争。dipatch barriers解决读写锁问题的放在这里处理。dispatch groups也是放在这里。

关于dispatch_once

使用dispatch_once创建单例时,要保证dispatch_once_t是全局或者static变量,保证只是创建一次。

+ (AppDataSource *)shareAppDataSource
{
    static AppDataSource *shareAppDataSource = nil;
    static dispatch_once_t predicate;
    dispatch_once(&predicate, ^{
        shareAppDataSource = [[self alloc] init];
    });
    return shareAppDataSource;
}

关于dispatch_async

异步执行api,将任务切换到工作线程中执行,避免阻塞主线程。比如读取网络数据,大数据IO,还有大量数据的数据库读写,这时需要在另一个线程中处理,然后通知主线程更新界面,GCD使用起来比NSThread和NSOperation方法要简单方便。

//代码框架,异步执行,完成后主线程更新UI
dispatch_async(dispatch_get_globle_queue(), ^(void){ 
  // do actual processing here 
  dispatch_async(dispatch_get_main_queue(), ^(void){
     handler(YES);
   }); 
});

下载图片示例

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
  NSURL * url = [NSURL URLWithString:@"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg"]; 
  NSData * data = [[NSData alloc]initWithContentsOfURL:url];  
  UIImage *image = [[UIImage alloc]initWithData:data]; 
  if (data != nil) { 
    dispatch_async(dispatch_get_main_queue(), ^{ 
      self.imageView.image = image;
     });
   }
});

关于dispatch_after延后执行

延时提交block,不等同与延时立即执行。介绍dispatch_after之前先了解一下dispatch time.

dispatch_time_t dispatch_time ( dispatch_time_t when, int64_t delta );
  • 第一个参数表示从什么时间开始,DISPATCH_TIME_NOW表示从当前开始
  • 第二个参数表示延时的时间,单文是纳秒,一秒钟等于1000000000纳秒,系统提供一些宏定义来简化计算
#define NSEC_PER_SEC 1000000000ull //每秒有多少纳秒 
#define USEC_PER_SEC 1000000ull //每秒有多少毫秒 
#define NSEC_PER_USEC 1000ull //每毫秒有多少纳秒

如果想要延时一秒,可以这样写

dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
dispatch_time(DISPATCH_TIME_NOW, 1000 * USEC_PER_SEC);
dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC * NSEC_PER_USEC);

延时示例实现

double delayInSeconds = 2.0; //延时时间
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC)); //从现在起延时delayInSeconds执行
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){     
    [self bar]; 
});//主线程延迟2s后执行bar方法。

关于dispatch_barrier_async

Dispatch Barrier确保提交的闭包是指定队列中在特定时段唯一在执行的一个。在所有先于Dispatch Barrier的任务都完成的情况下这个闭包才开始执行。轮到这个闭包时barrier会执行这个闭包并且确保队列在此过程不会执行其它任务。闭包完成后队列恢复。需要注意dispatch_barrier_async只在自己创建的队列上有这种作用,在全局并发队列和串行队列上,效果和dispatch_sync一样。

赋值示例

self.isolationQueue = dispatch_queue_create([label UTF8String], DISPATCH_QUEUE_CONCURRENT);

- (void)setCount:(NSUInteger)count forKey:(NSString *)key{ 
  key = [key copy]; 
  //确保所有barrier都是async异步的   
  dispatch_barrier_async(self.isolationQueue, ^(){ 
    if (count == 0) {
       [self.counts removeObjectForKey:key]; 
    } else { 
      self.counts[key] = @(count); 
    } 
  });
}
//防止文件读写冲突,可以创建一个串行队列,操作都在这个队列中进行,没有更新数据读用并行,写用串行。 
dispatch_queue_t dataQueue = dispatch_queue_create("com.fineway.dataqueue", DISPATCH_QUEUE_CONCURRENT); 
dispatch_async(dataQueue, ^{ 
 [NSThread sleepForTimeInterval:2.f]; 
  NSLog(@"read data 1"); 
}); 
dispatch_async(dataQueue, ^{ 
  NSLog(@"read data 2"); 
}); 
//等待前面的都完成,在执行barrier后面的 
dispatch_barrier_async(dataQueue, ^{ 
  NSLog(@"write data 1"); 
  [NSThread sleepForTimeInterval:1]; 
}); 
dispatch_async(dataQueue, ^{ 
  [NSThread sleepForTimeInterval:1.f]; 
  NSLog(@"read data 3"); 
}); 
dispatch_async(dataQueue, ^{ 
  NSLog(@"read data 4"); 
});

关于dispatch_apply

功能:把一项任务提交到队列中多次执行,具体是并行执行还是串行执行由队列本身决定.注意,dispatch_apply不会立刻返回,在执行完毕后才会返回,是同步的调用。

for (size_t y = 0; y < height; ++y) { 
  for (size_t x = 0; x < width; ++x) { 
    // Do something with x and y here 
  }
}
//因为可以并行执行,所以使用dispatch_apply可以运行的更快- 
(void)dispatchApplyDemo { dispatch_queue_t concurrentQueue = dispatch_queue_create("com.fineway.concurrentqueue", DISPATCH_QUEUE_CONCURRENT); 
  dispatch_apply(10, concurrentQueue, ^(size_t i) { 
    NSLog(@"%zu",i); 
  }); 
NSLog(@"The end"); //这里有个需要注意的是,dispatch_apply这个是会阻塞当前线程的。这个log打印会在dispatch_apply都结束后才开始执行
}

dispatch_apply能够避免线程爆炸,因为GCD能够进行线程优化

- (void)dealWiththreadWithMaybeExplode:(BOOL)explode {   
  dispatch_queue_t concurrentQueue = dispatch_queue_create("com.fineway.concurrentqueue",DISPATCH_QUEUE_CONCURRENT); 
  if (explode) { 
  //有问题的情况,可能会死锁 
  for (int i = 0; i < 999 ; i++) { 
    dispatch_async(concurrentQueue, ^{ 
      NSLog(@"wrong %d",i);
     //do something hard 
    }); 
  } 
  } else { 
  //会优化很多,能够利用GCD管理 
  dispatch_apply(999, concurrentQueue, ^(size_t i){
   NSLog(@"correct %zu",i); 
    //do something hard
   }); 
}
}

关于dispatch_group

dispatch groups是专门用来监视多个异步任务。dispatch_group_t实例用来追踪不同队列中的不同任务。

当group里所有事件都完成GCD API有两种方式发送通知,第一种是dispatch_group_wait,会阻塞当前进程,等所有任务都完成或等待超时。第二种方法是使用dispatch_group_notify,异步执行闭包,不会阻塞。

//dispatch_group_notify
- (void)dispatchGroupNotifyDemo { 
  dispatch_queue_t concurrentQueue = dispatch_queue_create("com.fineway.concurrentqueue",DISPATCH_QUEUE_CONCURRENT); 
  dispatch_group_t group = dispatch_group_create(); 
  dispatch_group_async(group, concurrentQueue, ^{ 
    NSLog(@"1"); 
  }); 
  dispatch_group_async(group, concurrentQueue, ^{ 
    NSLog(@"2"); 
  }); 
  dispatch_group_notify(group, dispatch_get_main_queue(), ^{ 
    NSLog(@"end"); 
  }); 
  NSLog(@"can continue");
  }
//dispatch_group_wait
- (void)dispatchGroupWaitDemo { 
  dispatch_queue_t concurrentQueue = dispatch_queue_create("com.fineway.concurrentqueue",DISPATCH_QUEUE_CONCURRENT); 
  dispatch_group_t group = dispatch_group_create();
//在group中添加队列的block 
  dispatch_group_async(group, concurrentQueue, ^{ 
    [NSThread sleepForTimeInterval:2.f]; 
    NSLog(@"1"); 
  }); 
  dispatch_group_async(group, concurrentQueue, ^{ 
    NSLog(@"2"); 
  }); 
  dispatch_group_wait(group, DISPATCH_TIME_FOREVER); 
  NSLog(@"can continue");
}

Dispatch IO 文件操作

dispatch io读取文件的方式类似于下面的方式,多个线程去读取文件的切片数据,对于大的数据文件这样会比单线程要快很多。

dispatch_async(queue,^{/*read 0-99 bytes*/});
dispatch_async(queue,^{/*read 100-199 bytes*/});
dispatch_async(queue,^{/*read 200-299 bytes*/});
  • dispatch_io_create:创建dispatch io
  • dispatch_io_set_low_water:指定切割文件大小
  • dispatch_io_read:读取切割的文件然后合并。
    苹果系统日志API里用到了这个技术,可以在这里查看:相关资料
pipe_q = dispatch_queue_create("PipeQ", NULL);//创建
pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){ 
    close(fd);
});
*out_fd = fdpair[1];
//设置切割大小
dispatch_io_set_low_water(pipe_channel, SIZE_MAX);
dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){ 
    if (err == 0) { 
        size_t len = dispatch_data_get_size(pipedata); 
        if (len > 0) { 
        //对每次切块数据的处理 
            const char *bytes = NULL; 
            char *encoded; 
            uint32_t eval; 
            dispatch_data_t md = dispatch_data_create_map(pipedata, (const void **)&bytes, &len); 
            encoded = asl_core_encode_buffer(bytes, len); 
            asl_msg_set_key_val(aux, ASL_KEY_AUX_DATA, encoded); 
            free(encoded); 
            eval = _asl_evaluate_send(NULL, (aslmsg)aux, -1); 
            _asl_send_message(NULL, eval, aux, NULL); 
            asl_msg_release(aux); 
            dispatch_release(md); 
        } 
    } 
    if (done) { 
        //semaphore +1使得不需要再等待继续执行下去。 
        dispatch_semaphore_signal(sem); 
        dispatch_release(pipe_channel); 
        dispatch_release(pipe_q); 
    }
});

Dispatch Source 用GCD监视进程

Dispatch Source用于监听系统的底层对象,比如文件描述符,Mach端口,信号量等。主要处理的事件如下表
方法 说明
DISPATCH_SOURCE_TYPE_DATA_ADD 数据增加
DISPATCH_SOURCE_TYPE_DATA_OR 数据OR
DISPATCH_SOURCE_TYPE_MACH_SEND Mach端口发送
DISPATCH_SOURCE_TYPE_MACH_RECV Mach端口接收
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 内存情况
DISPATCH_SOURCE_TYPE_PROC 进程事件
DISPATCH_SOURCE_TYPE_READ 读数据
DISPATCH_SOURCE_TYPE_SIGNAL 信号
DISPATCH_SOURCE_TYPE_TIMER 定时器
DISPATCH_SOURCE_TYPE_VNODE 文件系统变化
DISPATCH_SOURCE_TYPE_WRITE 文件写入

方法

  • dispatch_source_create:创建dispatch source,创建后会处于挂起状态进行事件接收,需要设置事件处理handler进行事件处理。
  • dispatch_source_set_event_handler:设置事件处理handler
  • dispatch_source_set_cancel_handler:事件取消handler,就是在dispatch source释放前做些清理的事。
  • dispatch_source_cancel:关闭dispatch source,设置的事件处理handler不会被执行,已经执行的事件handler不会取消。

关于Dispatch Semaphore

另外一种保证同步的方法。使用dispatch_semaphore_signal加1dispatch_semaphore_wait减1,为0时等待的设置方式来达到线程同步的目的和同步锁一样能够解决资源抢占的问题。


    //创建semaphore
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"start");
        [NSThread sleepForTimeInterval:1.f];
        NSLog(@"semaphore +1");
        dispatch_semaphore_signal(semaphore); //+1 semaphore
    });
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//知道signal发出后才执行
    NSLog(@"continue");

dispatch_suspend和dispatch_resume挂起和恢复队列

只能挂起未执行的block,不能挂起已经执行或者正在执行的block

GCD死锁

当串行队列里面同步执行该串行队列的时候,就会发生死锁,解决的方法就是将同步的串行队列放到另外一个线程就能够解决。

//示例1
 NSLog(@"1");
 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        NSLog(@"2");
    });
    NSLog(@"3");
//只打印1,2和3相互等待,不会打印,形成死锁

//示例2
    dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1");
    dispatch_async(serialQueue, ^{
        NSLog(@"2");
        //串行队列里面同步一个串行队列就会死锁
        dispatch_sync(serialQueue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
//只打印1 5 2,3与4不会打印

//示例3
        NSLog(@"1");
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"2");
            //将同步的串行队列放到另外一个线程就能够解决
            dispatch_sync(dispatch_get_main_queue(), ^{
                NSLog(@"3");
            });
            NSLog(@"4");
        });
        NSLog(@"5");

//示例4
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1");
        //回到主线程发现死循环后面就没法执行了
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"2");
        });
        NSLog(@"3");
    });
    NSLog(@"4");
    //死循环
    while (1) {
        //
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容