一次性搞懂 GCD的所有用法

GCD.png
讲GCD之前,必须得说说线程

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 以下是 iOS 中 4 套多线程方案

  • Pthreads : POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程。
  • NSThread : 是三种方法里面相对轻量级的,但需要管理线程的生命周期、同步、加锁问题,这会导致一定的性能开销
  • Grand Central Dispatch(简称GCD,iOS4才开始支持):提供了一些新特性、运行库来支持多核并行编程,它的关注点更高:如何在多个cpu上提升效率
  • NSOperation & NSOperationQueue : 苹果公司对 GCD 的封装,完全面向对象,NSOperation以面向对象的方式封装了需要执行的操作,不必关心线程管理、同步等问题。NSOperation是一个抽象基类,iOS提供了两种默认实现:NSInvocationOperation和NSBlockOperation,当然也可以自定义NSOperation
Pthreads的函数我们几乎不会用到
NSThread倒是有一些API,使用很方便来看一下
//取消线程
- (void)cancel;

//启动线程
- (void)start;

//判断某个线程的状态的属性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;

//设置和获取线程名字
-(void)setName:(NSString *)n;
-(NSString *)name;

//获取当前线程信息
+ (NSThread *)currentThread;

//获取主线程信息
+ (NSThread *)mainThread;

//使当前线程暂停一段时间,或者暂停到某个时刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;

主角出场

Grand Central Dispatch

GCD开源地址
GCD的好处在于它不需要管理线程的生命周期,线程的创建与分配,它是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核。

来看看怎么使用GCD

描述 说明
queue 队列
main 主队列
global 全局队列
dispatch_queue_t 描述队列
dispatch_block_t 描述任务
dispatch_once_t 描述一次性
dispatch_time_t 描述时间
dispatch_group_t 描述队列组
dispatch_semaphore_t 描述信号量
函数 说明
dispatch_sync() 同步执行
dispatch_async() 异步执行
dispatch_after() 延时执行
dispatch_once() 一次性执行
dispatch_apply() 快速遍历
dispatch_queue_create() 创建队列
dispatch_group_create() 创建队列组
dispatch_group_async() 提交任务到队列组
dispatch_group_enter()
dispatch_group_leave() 进入、离开组队列
dispatch_group_notify() 监听队列组执行完
dispatch_group_wait() 设置等待时间
  • 同步异步线程创建
//同步线程
dispatch_sync(队列, ^(block)) 阻塞线程
//异步线程
dispatch_async(队列, ^(block)) 不阻塞线程
  • 系统标准两个队列
//全局队列,一个并行的队列
dispatch_get_global_queue
//主队列,主线程中的唯一队列,一个串行队列
dispatch_get_main_queue
  • 自定义队列
//串行队列
dispatch_queue_create("com.boundless.serial", DISPATCH_QUEUE_SERIAL)
dispatch_queue_create("com.boundless.serial", NULL) // NULL默认为串行
//并行队列
dispatch_queue_create("com.boundless.concurrent", DISPATCH_QUEUE_CONCURRENT)

//队列优先级
* DISPATCH_QUEUE_PRIORITY_HIGH   优先级高
* DISPATCH_QUEUE_PRIORITY_DEFAULT  优先级默认
* DISPATCH_QUEUE_PRIORITY_LOW   优先级低
* DISPATCH_QUEUE_PRIORITY_BACKGROUND 优先级最低  表示用户不会察觉的任务,使用它来处理预加载,或者不需要用户交互和对时间不敏感的任务

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 可直接设置优先级
自定义队列优先级设置方法
   dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0);
    dispatch_queue_t custom_concrrent_priority_queue = dispatch_queue_create("com.boundless.concurrent", attr);

何时使用何种队列类型

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

看代码

    /** 全局并行队列 */
    dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    /** 主串行队列 */
    dispatch_queue_t main_queue = dispatch_get_main_queue();
    /** 自定义串行队列 */
    dispatch_queue_t custom_serial_queue = dispatch_queue_create("com.boundless.serial", DISPATCH_QUEUE_CONCURRENT);
    /** 自定义并发队列 */
    dispatch_queue_t custom_concrrent_queue = dispatch_queue_create("com.boundless.concurrent", DISPATCH_QUEUE_CONCURRENT);
    /** 自定义队列设置优先级 */
    dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0);
    dispatch_queue_t custom_concrrent_priority_queue = dispatch_queue_create("com.boundless.concurrent", attr);
    
    /** 任务(^{})在全局队列(global_queue)中同步执行(dispatch_sync) */
    dispatch_sync(global_queue, ^{
        NSLog(@"执行任务");
    });
    /** 任务(^{})在全主队列(main_queue)中同步执行(dispatch_sync) */
    dispatch_sync(main_queue, ^{
        NSLog(@"执行任务");
    });
    
    /** 任务(^{})在全局队列(global_queue)中异步执行(dispatch_async) */
    dispatch_async(global_queue, ^{
        NSLog(@"执行任务");
    });
    /** 任务(^{})在全主队列(main_queue)中异步执行(dispatch_async) */
    dispatch_async(main_queue, ^{
        NSLog(@"执行任务");
    });

开发中常用的一些GCD函数
1.耗时操作完成回到主线程
dispatch_async(global_queue, ^{
        NSLog(@"执行任务");
      dispatch_async(main_queue, ^{
              NSLog(@"回到主线程刷新UI");
          });
    });  
2.延迟执行
  • #define NSEC_PER_SEC 1000000000ull //每秒有多少纳秒
  • #define USEC_PER_SEC 1000000ull //每秒有多少毫秒
  • #define NSEC_PER_USEC 1000ull //每毫秒有多少纳秒
/**
 延迟执行
 _func_ dispatch_time(<#dispatch_time_t when#>, <#int64_t delta#>)
 _func_ dispatch_after(<#dispatch_time_t when#>, <#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)
 
 */
- (void)func_dispatch_after{
    // 延迟时间
    double delayInSeconds = 2.0;
    dispatch_time_t time_source = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
    
    // 主队列中延迟执行
    dispatch_after(time_source, dispatch_get_main_queue(), ^{
        
    });
}
3.执行一次(单例)
/**
 执行一次
 _func_ dispatch_once(<#dispatch_once_t * _Nonnull predicate#>, <#^(void)block#>)
 @return
 */
+ (instancetype)func_dispatch_Once{
    static GCDVC *_singleVC = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _singleVC = [GCDVC new];
    });
    return _singleVC;
}
4.队列组( 可以使用 queue 的队列组)
  • dispatch_group_create() //创建组
  • dispatch_group_async(dispatch_group_t _Nonnull group, dispatch_queue_t _Nonnull queue, ^(void)block) //添加任务到相应队列组
  • dispatch_group_notify(dispatch_group_t _Nonnull group, dispatch_queue_t _Nonnull queue, ^(void)block) //通知,不阻塞线程
  • dispatch_group_wait(dispatch_group_t _Nonnull group, dispatch_time_t timeout) //阻塞线程到队列任务执行完
/**
 队列组
 可以使用 queue 的队列组
 */
- (void)func_dispatch_groups{
    // 组
    dispatch_group_t group = dispatch_group_create();
    // 全局队列
    dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_group_async(group, global_queue, ^{
        NSLog(@"任务 1");
    });
    
    dispatch_group_async(group, global_queue, ^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"任务 2");
    });
    
    dispatch_group_async(group, global_queue, ^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"任务 3");
    });
    
    /** 判断 队列组全部执行完成 的两个方法*/
    
    // dispatch_group_wait(group, DISPATCH_TIME_FOREVER); 阻塞当前线程 直到队列组任务执行完
    
    // 队列组任务执行完 接到通知 ,不会阻塞线程
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"所有执行完成");
    });
    
}

5.队列组 ( 无法直接使用队列变量(queue)的 打包组)
  • dispatch_group_enter(dispatch_group_t _Nonnull group)
  • dispatch_group_leave(dispatch_group_t _Nonnull group)
/**
 队列组
 无法直接使用队列变量(queue)的 打包组
 */
- (void)func_dispatch_enterAndLeave{
    // URL
    NSString *URLString = [NSString stringWithFormat:@"http://www.baidu.com"];
    NSURL *URL = [[NSURL alloc] initWithString:URLString];
    
    // session
    NSURLSession *sessionManager = [NSURLSession sharedSession];
    sessionManager.configuration.timeoutIntervalForRequest = 20;
    sessionManager.configuration.timeoutIntervalForResource = 20;
    
    // 创建 group
    dispatch_group_t group = dispatch_group_create();
    
    // task
    // 进入group
    dispatch_group_enter(group);
    NSURLSessionTask *task =
    [sessionManager dataTaskWithURL:URL
                  completionHandler:^(NSData * _Nullable data,
                                      NSURLResponse * _Nullable response,
                                      NSError * _Nullable error) {
                      NSLog(@"拿到数据");
                      // 离开group
                      dispatch_group_leave(group);
                  }];
    
    // resume
    [task resume];
}

自己创建队列:使用dispatch_group_async。
无法直接使用队列变量(如使用AFNetworking添加异步任务):使用dispatch_group_enter,dispatch_group_leave。
使用dispatch_group_enterdispatch_group_leave就可以方便的将一系列网络请求“打包”起来~
添加结束任务
添加结束任务也可以分为两种情况,如下:
在当前线程阻塞的同步等待:dispatch_group_wait。
添加一个异步执行的任务作为结束任务:dispatch_group_notify

6.快速遍历
  • dispatch_apply(size_t iterations, dispatch_queue_t _Nonnull queue, ^(size_t)block)
  • dispatch_apply 的作用是在一个队列上“运行”多次block,其实就是简化了用循环去向队列依次添加block任务
  • dispatch_apply 会开启线程 快速执行完循环
  • dispatch_apply (阻塞当前线程,直到循环执行完成)
/**
 快速遍历
 */
- (void)func_dispatch_apply{
    dispatch_queue_t custom_concurrent_queue = dispatch_queue_create("com.boundless.concurrent", DISPATCH_QUEUE_CONCURRENT);
    dispatch_apply(50/** 循环次数 */, custom_concurrent_queue, ^(size_t i) {
        NSLog(@"%zu",i);
    });
}
7.栅栏、用于 解决并发队列中 读写同一个资源抢夺的情况

dispatch_barrier_async(dispatch_queue_t _Nonnull queue, ^(void)block)
dispatch_barrier_sync(dispatch_queue_t _Nonnull queue, ^(void)block)

  • dispatch_barrier_async(异步,不会阻塞当前线程) 会设置栅栏 栅栏之后的任务会等到栅栏之前的任务执行完成再执行
  • dispatch_barrier_async 必须配合 DISPATCH_QUEUE_CONCURRENT( 自定义的并发队列),其他队列 dispatch_barrier_async的作用和 dispatch_async 一样
  • dispatch_barrier_sync(同步,阻塞当前线程 ) 其他和 dispatch_barrier_async 一样
/**
 栅栏
 用于 解决并发队列中 读写同一个资源抢夺的情况
 */
- (void)func_dispatch_barrier_async{
    dispatch_queue_t custom_concurrent_queue = dispatch_queue_create("com.boundless.concurrent", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(custom_concurrent_queue, ^{
        NSLog(@"写入数据 1");
    });
    
    dispatch_async(custom_concurrent_queue,^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"写入数据 2");
    });
    dispatch_barrier_async(custom_concurrent_queue, ^{
        NSLog(@"等待数据写入完毕");
    });
    
    dispatch_async(custom_concurrent_queue, ^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"读取数据 1");
    });
    
    dispatch_async(custom_concurrent_queue, ^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"读取数据 2");
    });
    
}
8.队列挂起/队列重启
  • dispatch_suspend(dispatch_object_t _Nonnull object)
  • dispatch_resume(dispatch_object_t _Nonnull object)
  • dispatch_suspend 不能阻止正在执行的任务 只会阻止还未执行的任务
/**
 队列挂起/队列重启
 */
- (void)func_dispatch_suspendAndResume{
    dispatch_queue_t custom_concurrent_queue = dispatch_queue_create("com.boundless.concurrent", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(custom_concurrent_queue, ^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"执行任务 1");
    });
    
    dispatch_async(custom_concurrent_queue,^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"执行任务 2");
    });
    
    dispatch_suspend(custom_concurrent_queue);
    [NSThread sleepForTimeInterval:1.0];
    dispatch_resume(custom_concurrent_queue);
}
9.信号量 Dispatch Semaphore保证同步的方法

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

  • dispatch_semaphore_create(long value)
  • dispatch_semaphore_signal(dispatch_semaphore_t _Nonnull dsema)
  • dispatch_semaphore_wait(dispatch_semaphore_t _Nonnull dsema, dispatch_time_t timeout)
dispatch_semaphore_t signal = dispatch_semaphore_create(1); //传入值必须 >=0, 若传入为0则阻塞线程并等待timeout,时间到后会执行其后的语句
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);

//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"线程1 等待ing");
    dispatch_semaphore_wait(signal, overTime); //signal 值 -1
    NSLog(@"线程1");
    dispatch_semaphore_signal(signal); //signal 值 +1
    NSLog(@"线程1 发送信号");
    NSLog(@"--------------------------------------------------------");
});

//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"线程2 等待ing");
    dispatch_semaphore_wait(signal, overTime);
    NSLog(@"线程2");
    dispatch_semaphore_signal(signal);
    NSLog(@"线程2 发送信号");
});

关于信号量,我们可以用停车来比喻:

停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。
信号量的值(signal)就相当于剩余车位的数目,dispatch_semaphore_wait 函数就相当于来了一辆车,dispatch_semaphore_signal 就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了(dispatch_semaphore_create(long value)),调用一次 dispatch_semaphore_signal,剩余的车位就增加一个;调用一次dispatch_semaphore_wait 剩余车位就减少一个;当剩余车位为 0 时,再来车(即调用 dispatch_semaphore_wait)就只能等待。有可能同时有几辆车等待一个停车位。有些车主没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,所以就一直等下去。

10.dispatch_queue_set_specific、dispatch_get_specific

FMDB利用它来防止死锁
作用类似objc_setAssociatedObject跟objc_getAssociatedObject

 static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey;
 //创建串行队列,所有数据库的操作都在这个队列里
 _queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
 //标记队列
 dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);
 
 //检查是否是同一个队列来避免死锁的方法
 - (void)inDatabase:(void (^)(FMDatabase *db))block {
 FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
 assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");
 }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容