GCD

GCD(Grand Central Dispatch)

GCD 中其实没有线程的说法,只有队列的说法。习惯使用线程的开发者(例如使用 pthread_t 的 C 语言开发者)可能不太习惯这一点。队列是对线程的封装,避免我们直接操作线程,我们只需要会使用队列就行了。GCD 是苹果内核特有的,可以充分利用多核。

常用的一个代码片段

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    //耗时的操作
    dispatch_async(dispatch_get_main_queue(), ^{
        //回到了主队列更新UI
    });
});

队列

队列的定义:一种先进先出(FIFO)的线性表。

在 GCD 中队列分为两种:串行队列(Serial Dispatch queue)和并行队列(Concurrent Dispatch Queue)。

任务通过 Block 的形式添加到队列中。在串行队列中,后一个任务必须等待前一个任务执行完毕才能执行;在并行队列中,则不需要等待前一个任务执行完毕,因此可以并行执行多个任务。

//创建串行队列
dispatch_queue_t queue1 = dispatch_queue_create("q1", DISPATCH_QUEUE_SERIAL);
//将 DISPATCH_QUEUE_SERIAL 替换成 NULL 后创建的也是串行队列

//创建并行队列
dispatch_queue_t queue2 = dispatch_queue_create("q2", DISPATCH_QUEUE_CONCURRENT);

在ARC系统版本高于iOS6,已经不需要调用dispatch_release来释放队列了。

在开发中常用的两个队列:主队列(Main Dispatch Queue)和全局队列(Global Dispatch Queue)。主队列是 串行队列 ,全局队列是 并行队列

//创建主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();

//创建全局队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

全局队列有 4 个优先级:

#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_sync)和 异步(dispatch_async)

这两个函数都可以把任务添加到队列中,到底有什么区别呢?以下是我从官方文档中截取的一些描述:

对于dispatch_async的一些描述:

Submits a block for asynchronous execution on a dispatch queue.
Calls to dispatch_async() always return immediately after the block has been submitted, and never wait for the block to be invoked.

对于dispatch_sync的一些描述:

Submits a block for synchronous execution on a dispatch queue.
Submits a block to a dispatch queue like dispatch_async(), however dispatch_sync() will not return until the block has finished.

dispatch_sync 是向队列添加一个同步执行的 Block,直到该 Block 完成才会返回。dispatch_async 是向队列添加一个异步执行的 Block,添加后立即返回,永远不会等待该 Block 被调用。

dispatch_sync(dispatch_queue_create("q1", NULL), ^{
    NSLog(@"操作1");
});
NSLog(@"操作2");
// 先打印“操作1”,再打印“操作2”

dispatch_async(dispatch_queue_create("q2", NULL), ^{
    NSLog(@"操作3");
});
NSLog(@"操作4");
// "操作3"和“操作4”的打印顺序不固定

总结:
dispatch_sync 做了两个事情:

  1. 将 Block 添加到队列;
  2. 阻塞调用线程,等待 Block 执行结束,回到调用线程。

dispatch_async 也做了两个事情:

  1. 将 Block 添加到队列;
  2. 直接回到调用线程,不阻塞调用线程。

顺便提一下,dispatch_sync_f 跟 dispatch_sync 的区别是使用函数指针代替了 Block,用法如下:

void func(void *name) {
    NSLog(@"hi %@", name);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_async_f(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), @"qhd", &func);
}

线程

队列与线程没有一一对应的关系,串行队列不一定只用到一个线程,并行队列不一定会用到多个线程。但主队列(Main Dispatch Queue)比较特殊,主队列对应了主线程,即主队列的任务一定是在主线程中执行的。是否开启新线程与添加任务到队列的方式有关,即与 同步(dispatch_sync)和 异步(dispatch_async)相关。

在同一个队列中的任务并不一定都在同一个线程中执行。看以下例子:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t q1 = dispatch_queue_create("q1", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(q1, ^{
        NSLog(@"操作1:%@ %@", [NSThread currentThread], ([[NSThread currentThread] isMainThread] ? @"主线程" : @"非主线程"));
    });

    dispatch_async(q1, ^{
        NSLog(@"操作2:%@ %@", [NSThread currentThread], ([[NSThread currentThread] isMainThread] ? @"主线程" : @"非主线程"));
    });
}

/*
 打印了:
 操作1:<NSThread: 0x6100000765c0>{number = 1, name = main} 主线程
 操作2:<NSThread: 0x618000077ec0>{number = 3, name = (null)} 非主线程
*/

以上例子,“操作1” 和 “操作2” 打印出来的线程地址是不同的,说明两个任务即使是在同一个串行队列中,也不在同一个线程上执行。把以上例子的串行队列换成并行队列(即 DISPATCH_QUEUE_SERIAL 换成 DISPATCH_QUEUE_CONCURRENT),“操作1” 和 “操作2” 还是分别在两个线程上,这里就不贴代码了。以上例子表明即使是在同一个队列中,“操作1” 和 “操作2” 是在不同线程中执行的,说明了 一个队列可以对应着多个线程。再看 “操作1” 竟然是在主线程中执行的,主队列也是在主线程中的,说明了 可以多个队列对应着同一个线程

既然队列不能决定是否开启线程,那要队列何用?可是队列能决定任务的先后顺序啊。

队列、同步(异步)、线程三者的关系是怎样的?文章开头就已经说了在使用GCD中,开发者不需要知道线程,只需知道队列就可以。为什么这样说,其实是否开启新线程是由GCD根据资源的分配管理来决定的。网上有些博客总结出队列、同步(异步)、线程三者的关系表如下:

并行队列 串行队列 主队列
同步 1.不开启新线程,串行执行任务 3.不开启新线程,串行执行任务 5.不开启新线程,串行执行任务
异步 2.开启新线程,并行执行任务 4.开启新线程,串行执行任务 6.不开启新线程,串行执行任务

其实这个表不完全正确,因为这个表没有说明调用同步操作或异步操作时是在怎样的队列中调用的。我们先把调用同步函数或异步函数所在的队列称为“调用队列”,同步函数或异步函数的第一个参数称为“目标队列”,如果调用队列与目标队列不相同的情况下,表中的描述是正确的,但是如果调用队列与目标队列是相同的情况下,表中有些描述就不对了,下面举两例子。

举例一:如果当调用队列是主队列时,执行 5 的情况,即在主队列中执行同步操作添加一个任务到主队列中,会形成死锁,至于死锁原因会在后面讲到。

举例二:如果当调用队列是并行队列,目标队列也是并行队列,并且调用队列与目标队列是同一个队列时,2 的情况的描述未必会正确。看以下代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t queue = dispatch_queue_create("q", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"标记1:%@", [NSThread currentThread]);
        
        dispatch_async(queue, ^{
            NSLog(@"标记2:%@", [NSThread currentThread]);
        });
        
        dispatch_async(queue, ^{
            NSLog(@"标记3:%@", [NSThread currentThread]);
        });
    });

}

/*
 打印如下结果:
 标记1:<NSThread: 0x61000007d480>{number = 3, name = (null)}
 标记2:<NSThread: 0x61000007d480>{number = 3, name = (null)}
 标记3:<NSThread: 0x61800007ad40>{number = 4, name = (null)}
*/

以上这个例子,“标记2” 是异步添加任务到并行队列中,但是 “标记1” 和 “标记2” 竟然是在同一个线程中的,并没有开启一个新线程,所以 “异步 + 并行队列” 并不一定会开启新线程。“标记3” 却开启了一个新线程,理论上 “标记2” 和 “标记3” 是同等级别的,造成的结果却不一样。

基于上述例子上,我们再来看一个扩充版例子:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t queue = dispatch_queue_create("q", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"标记1:%@", [NSThread currentThread]);
        
        dispatch_async(queue, ^{
            NSLog(@"标记2:%@", [NSThread currentThread]);
        });
        
        dispatch_async(queue, ^{
            NSLog(@"标记3:%@", [NSThread currentThread]);
            
            dispatch_async(queue, ^{
                NSLog(@"标记4:%@", [NSThread currentThread]);
            });
            
            dispatch_async(queue, ^{
                NSLog(@"标记5:%@", [NSThread currentThread]);
            });

        });
    });
    
}

/*
 打印如下结果:
 标记1:<NSThread: 0x608000262340>{number = 3, name = (null)}
 标记2:<NSThread: 0x608000262340>{number = 3, name = (null)}
 标记3:<NSThread: 0x610000266b00>{number = 4, name = (null)}
 标记4:<NSThread: 0x610000266b00>{number = 4, name = (null)}
 标记5:<NSThread: 0x608000262340>{number = 3, name = (null)}
*/

“标记1”、“标记2”、“标记5”竟然在同一个线程中,而“标记3” 和 “标记4”同在另外一个线程中。举这个例子想说明,GCD对线程的管理不是那么简单,会自动根据资源分配来调度任务。

死锁

本文例子造成死锁的原因不需要用上线程的知识,只需要理解队列和同步/异步就够了。

死锁案例一:

先看网上流传最广的例子:

int main(int argc, char * argv[]) {
    
    NSLog(@"任务1");

    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"任务2");
    });

    NSLog(@"任务3");

    return 0;
}

// 运行的时候报 EXC_BAD_INSTRUCTION 错误

很多人的解释是这样的:在主队列中 “任务2” 在等 “任务3”,“任务3” 在等 “任务2”,形成死锁。其实这样说是不够准确的,我们把所有 NSLog 语句去掉,甚至把 return 0 也去掉,去掉后就是以下的代码,还是会发生死锁:

int main(int argc, char * argv[]) {
    
    dispatch_sync(dispatch_get_main_queue(), ^{
        
    });

}

解析:在 main 函数里的任务,默认就是在主队列中执行的。在不管 dispatch_sync 具体是做什么的前提下,主队列的最后一个任务是调用 dispatch_sync 函数,我们把这个任务称为 A。然后再来看 dispatch_sync 的含义,是同步的意思,它会做两件事:1.把 Block 添加到队列中;2.把 Block 执行完毕。如果两件事都没完成,则 dispatch_sync 不会返回,所以说 dispatch_sync 包含了执行完 Block,我们把执行 Block 称为任务为 B,就是说 A 任务包含了 B 任务,要想完成 A 必须先完成 B。再接着看,B 是被添加到主队列的,主队列是串行队列,添加后 B 就变成主队列的最后一个任务了,那么现在主队列现在排着两个任务按顺序依次是 A、B,因为是串行队列,所以必须先运行完 A 才能运行 B,根据前面分析,想要完成 A 必须先完成 B,这就形成了死锁。

死锁案例一原理图片

死锁案例二:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t q1 = dispatch_queue_create("q1", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(q1, ^{
        
        NSLog(@"A");
        
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"B");
        });
        
    });
    
    dispatch_sync(q1, ^{
        NSLog(@"C");
    });
}

案例二形成死锁比较隐蔽,并不会像案例一那样报运行错误,但是不会执行 NSLog(@"B")NSLog(@"C"),形成了假死状态,但是又不会闪退。我们来分解一下,dispatch_async 操作称为任务 D1,代码从上到下第一个 dispatch_sync 称为任务 D2,第二个 dispatch_sync 称为任务 D3。D1 里面的 Block 称为 B1,D2 里面的 Block 称为 B2,D3 里面的 Block 称为 B3。

D1 、D3 、B2 在主队列(串行队列) MainQueue 中,B1、D2、B3 在串行队列 q1 中。那么为什么 B3 不会执行呢?

  1. 因为 q1 是串行的,所以 B3 要等待 B1 完成;
  2. 因为 B1 包含了 D2,所以 B1 要等待 D2 完成;
  3. 因为 D2 是同步操作,所以 D2 要等待 B2 完成;
  4. 因为 B2 在主队列中,所以 B2 要等待 D3 完成;
  5. 因为 D3 是同步操作,所以 D3 要等待 B3 完成,所以形成了循环等待。

由于 B3 不执行,因此 B2 也不会执行。所以并不会执行到 NSLog(@"B")NSLog(@"C")

死锁案例二原理图片

总结

案例一在当前所在的队列跟目标队列是同一个队列,很容易发现问题,同一个线程内的任务互相等待形成死锁;而案例二,有两个串行队列,队列一的任务等待队列二的任务,队列二等待队列一的任务,形成死锁。如果多个队列形成的循环等待就更难难发现了。无论是“案例一”还是“案例二”,我们看到一个共同的东西,都是用了<在串行队列中 同步 串行队列>的形式。

总结:避免使用<在串行队列中 同步 串行队列>的形式。

dispatch_after

延时提交任务到队列,作用是跟延时调用 dispatch_async 的作用是一样的。注意 dispatch_after 不能保证在指定时间马上执行任务,只保证指定时间把任务添加到队列上。

 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), queue, ^{
     // 会在 2 秒后执行此闭包
 });

dispatch_time 函数的第一个参数的意思是基础时间,DISPATCH_TIME_NOW 表示当前时间,DISPATCH_TIME_FOREVER 表示时间无限大。

dispatch_time 函数的第二个参数的意思是纳秒数。时间单位的关系如下:

  • 1 秒(SEC) = 1000 毫秒(MSEC)
  • 1 毫秒(MSEC)= 1000 微秒(USEC)
  • 1 微秒(USEC) = 1000 纳秒(NSEC)

来看已经定义了的几个宏:

#define NSEC_PER_SEC 1000000000ull
#define NSEC_PER_MSEC 1000000ull
#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull

PER 是 “每” 的意思,所以以上四个宏的意义:

  • NSEC_PER_SEC:每一秒的纳秒数
  • NSEC_PER_MSEC:每一毫秒的纳秒数
  • USEC_PER_SEC:每一表的微秒数
  • NSEC_PER_USEC:每一微秒的纳秒数

所以 2 秒就含有 2 ** NSEC_PER_SEC 纳秒,500 毫秒就含有 500 ** NSEC_PER_MSEC 纳秒。

group

dispatch_group_async

功能是提交一个 block 到队列,并且关联到一个 group。

dispatch_group_notify

功能是当与 group 相关联的所有 block 已完成时,将一个 block 提交给队列。

用法示例:

 dispatch_group_t group = dispatch_group_create();
 
 dispatch_queue_t queue = dispatch_queue_create("q", DISPATCH_QUEUE_CONCURRENT);
 
 dispatch_group_async(group, queue, ^{
     sleep(2);
     NSLog(@"1");
 });
 
 dispatch_group_async(group, queue, ^{
     sleep(1);
     NSLog(@"2");
 });
 
 dispatch_group_notify(group, queue, ^{
     NSLog(@"finish");
 });
 
 NSLog(@"after");

 /*
    输出顺序是:
    after
    2
    1
    finish
 */

注意:如果把 dispatch_group_notify 写在 dispatch_group_async 的前面, 那么 dispatch_group_notify 提交的 block 可能先执行,因为一开始 group 内的任务就是空的。

dispatch_group_wait

功能是同步等待直到与 group 相关联的所有 block 完成或到了指定时间的时间。

dispatch_group_notifydispatch_group_wait 比较相似,区别是前者不阻塞调用线程,后者会阻塞调用线程。

基于以上例子,把 dispatch_group_notify 换成 dispatch_group_wait

dispatch_group_t group = dispatch_group_create();

dispatch_queue_t queue = dispatch_queue_create("q", DISPATCH_QUEUE_CONCURRENT);

dispatch_group_async(group, queue, ^{
    sleep(2);
    NSLog(@"1");
});

dispatch_group_async(group, queue, ^{
    sleep(1);
    NSLog(@"2");
});

dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_FOREVER, 0));

NSLog(@"after");

/*
   输出顺序是:
   2
   1
   after
*/

dispatch_group_enter

功能是手动标记有任务已经进入该 group。

dispatch_group_leave

功能是手动标记该 group 内的任务已经完成。

注意:
dispatch_group_enterdispatch_group_leave 要成对出现,如果只写了 dispatch_group_leave 而没写 dispatch_group_enter,会引起程序崩溃。

使用例子:

 dispatch_group_t group = dispatch_group_create();
 dispatch_queue_t queue = dispatch_queue_create("q", DISPATCH_QUEUE_CONCURRENT);
 
 dispatch_group_enter(group);
 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     sleep(2);
     NSLog(@"1");
     dispatch_group_leave(group);
 });
 
 dispatch_group_notify(group, queue, ^{
     NSLog(@"finish");
 });

 /*
   输出结果:
   1
   finish
 */

dispatch_barrier_async 、dispatch_barrier_sync

dispatch_barrier_async 提交一个异步执行的 block 到队列,该 block 作为一个分界线,队列中的任务顺序是:在该 block 之前的任务必须都执行完才执行 block,并且执行完该 block 才执行在它之后的任务。该方法只用在并行队列上。

例子

dispatch_queue_t queue = dispatch_queue_create("q", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
    sleep(3);
    NSLog(@"1");
});

dispatch_async(queue, ^{
    sleep(1);
    NSLog(@"2");
});

dispatch_barrier_async(queue, ^{
    NSLog(@"barrier_async");
});

NSLog(@"3");

dispatch_async(queue, ^{
    NSLog(@"4");
});

/*
  输出结果:
  3
  2
  1
  barrier_async
  4
*/

dispatch_barrier_syncdispatch_barrier_async 相似,只不过 dispatch_barrier_sync 会阻塞调用线程,而 dispatch_barrier_async 不阻塞调用个线程。例子:

dispatch_queue_t queue = dispatch_queue_create("q", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
    sleep(3);
    NSLog(@"1");
});

dispatch_async(queue, ^{
    sleep(1);
    NSLog(@"2");
});

dispatch_barrier_sync(queue, ^{
    NSLog(@"barrier_async");
});

NSLog(@"3");

dispatch_async(queue, ^{
    NSLog(@"4");
});

/*
  输出结果:
  2
  1
  barrier_async
  3
  4
*/

dispatch_set_target_queue

  1. 当第二个参数为并行队列,更改队列的优先级。。dispatch_queue_crete 创建的队列默认优先级是 DISPATCH_QUEUE_PRIORITY_DEFAULT。
dispatch_queue_t serialQueue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t globalQueue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(serialQueue, globalQueue);
  1. 当第二个参数为串行队列,可以改变队列的层次体系。多个队列(无论是串行还是并行)指定目标为一个串行队列,则相当于变成一个串行队列那样执行任务。
dispatch_queue_t concurrentQueue1 = dispatch_queue_create("concurrent1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t concurrentQueue2 = dispatch_queue_create("concurrent2", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t serialQueue1 = dispatch_queue_create("serial1", DISPATCH_QUEUE_SERIAL);

dispatch_queue_t targetQueue = dispatch_queue_create("target", DISPATCH_QUEUE_SERIAL);

dispatch_set_target_queue(concurrentQueue1, targetQueue);
dispatch_set_target_queue(concurrentQueue2, targetQueue);
dispatch_set_target_queue(serialQueue1, targetQueue);

dispatch_async(concurrentQueue1, ^{
    sleep(4);
    NSLog(@"c1 1 %@", [NSThread currentThread]);
});

dispatch_async(concurrentQueue2, ^{
    sleep(2);
    NSLog(@"c2 1 %@", [NSThread currentThread]);
});

dispatch_async(concurrentQueue1, ^{
    sleep(1);
    NSLog(@"c1 2 %@", [NSThread currentThread]);
});

dispatch_async(serialQueue1, ^{
    NSLog(@"s1 1 %@", [NSThread currentThread]);
});

dispatch_async(serialQueue1, ^{
    NSLog(@"s1 2 %@", [NSThread currentThread]);
});

/*
  输出结果:
  c1 1 <NSThread: 0x600000073980>{number = 3, name = (null)}
  c2 1 <NSThread: 0x600000073980>{number = 3, name = (null)}
  c1 2 <NSThread: 0x600000073980>{number = 3, name = (null)}
  s1 1 <NSThread: 0x600000073980>{number = 3, name = (null)}
  s1 2 <NSThread: 0x600000073980>{number = 3, name = (null)}
*/

上面这个例子,虽然有两个并行队列和一个串行队列,而且都是用了异步添加任务,但是任务并没有并发执行,而是像只有一个串行队列那样子执行。

参考

dispatch_apply (按指定次数将Block添加到队列)

将一个 block 多次添加到队列上,然后等待多次的 block 执行完成。

 dispatch_apply(5, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) , ^(size_t index) {
     NSLog(@"%@ %@", [@(index) stringValue], [NSThread currentThread]);
 });
 NSLog(@"done");

 /*
  输出结果:
  0 <NSThread: 0x6000002620c0>{number = 1, name = main}
  2 <NSThread: 0x610000265a00>{number = 4, name = (null)}
  1 <NSThread: 0x618000262c00>{number = 3, name = (null)}
  4 <NSThread: 0x6180002625c0>{number = 6, name = (null)}
  3 <NSThread: 0x610000265980>{number = 5, name = (null)}
  done
*/

注意 dispatch_apply 会阻塞调用线程,所以建议将 dispatch_apply 和 dispatch_async 函数一起结合使用。

dispatch_suspend、dispatch_resume

dispatch_suspend 挂起队列。
dispatch_resume 恢复队列。
挂起队列的时候,对已经开始执行的任务没影响,只是在队列中未执行的任务被挂起了。dispatch_suspend 和 dispatch_resume 一定要成对出现。

dispatch_semaphore_t、dispatch_semaphore_create、dispatch_semaphore_wait、dispatch_semaphore_signal

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

以上例子创建一个信号量,初始值是0。

dispatch_semaphore_wait 的作用是 等待 semaphore 的值等于或大于 1 。如果 semaphore 小于 1 ,则一直等待(阻塞调用线程),直至超时,超时的返回值是 非0 ;如果semaphore等于或大于 1 ,则对 semaphore 进行减 1 ,并且马上返回,返回值是 0 。

dispatch_semaphore_signal 是给信号量加一。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
    NSLog(@"1");
    long r = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"3 return value is: %@", [@(r) stringValue]);
    
});

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
    dispatch_semaphore_signal(semaphore);
    NSLog(@"2");
    
});

/*
  输出结果:
  1
  2
  3 return value is: 0
*/

另外还可以用来控制并发数量,例如有 100 个任务,最大并发数是 10

dispatch_group_t group = dispatch_group_create();
dispatch_semaphore_t semaphore = dispatch_semaphore_create(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);
    dispatch_group_async(group, queue, ^{
        NSLog(@"%i",i);
        sleep(2);
        dispatch_semaphore_signal(semaphore);
    });
}

dispatch_once

保证程序执行中只执行了一次,并且在多线程环境中也是百分之百安全的,通常生成单例对象时使用。

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
      
    });

io 相关的操作

dispatch_io_create:创建一个 dispatch_io_t 。

dispatch_io_set_low_water:设置一次最小读取的大小,也就是读取的大小不会小于指定的值,除非遇到 DISPATCH_IO_STRICT_INTERVAL 标志 或 EOF 或 错误。

dispatch_io_set_high_water:设置一次最大读取的大小,也就是读取的大小不会超过指定的值。

dispatch_io_read:读取数据。

NSString *path = [NSString stringWithFormat:@"%@/a.text", [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]];
[@"hello world" writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];

dispatch_fd_t fd = open(path.UTF8String, O_RDONLY);
dispatch_queue_t pipe_q = dispatch_queue_create("PipeQ", NULL);
dispatch_io_t pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int error) {
    close(fd);
});

dispatch_io_set_low_water(pipe_channel, 1);
dispatch_io_set_high_water(pipe_channel, 1);
long long fileSize = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil].fileSize;
NSMutableData *totalData = [[NSMutableData alloc] init];
dispatch_io_read(pipe_channel, 0, fileSize, pipe_q, ^(bool done, dispatch_data_t  _Nullable data, int error) {
    if (error == 0) {
        size_t len = dispatch_data_get_size(data);
        if (len > 0) {
            [totalData appendData:(NSData *)data];
        }
    }
    if (done) {
        NSString *str = [[NSString alloc] initWithData:totalData encoding:NSUTF8StringEncoding];
        NSLog(@"%@", str);
    }
});

GCD 实现

Dispatch Queue

无论编程人员如何努力编写管理线程的代码,在性能方面也不可能胜过 XNU 内核级所实现的 GCD。

我们尽量多使用 GCD 或者使用了 Cocoa 框架 GCD 的 NSOperationQueue 类等 API。

用于实现 Dispatch Queue 而使用的软件组件

组件名称 提供技术
libdispatch Dispatch Queue
Libc(pthreads) pthread_workqueue
XNU 内核 workqueue

Dispatch Source

Dispatch Sources 是基于 C 的系统事件异步处理机制。一个 Dispatch Source 封装了一个特定类型的系统事件,当事件发生时提交一个特定的 block 对象或函数到 dispatch queue。

你可以使用 Dispatch Sources 监控 以下类型的系统事件:

名称 内容
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_PROC 监测到与进程相关的事件
DISPATCH_SOURCE_TYPE_READ 可读取文件映像
DISPATCH_SOURCE_TYPE_SIGNAL 接收信号
DISPATCH_SOURCE_TYPE_TIMER 定时器
DISPATCH_SOURCE_TYPE_VNODE 文件系统有变更
DISPATCH_SOURCE_TYPE_WRITE 可写入文件映像

定时器例子

dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
if (timer) {
    dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), 3 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"hello");
    });
    dispatch_resume(timer);
    self.timer = timer;
}

更多例子参考Concurrency Programming Guide

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

推荐阅读更多精彩内容