目录
概念介绍
旨在替代NSThread等线程技术,充分利用设备的多核。
首先先看几个概念:
同步:在当前线程执行任务,不具备开启新线程的能力
dispatch_sync
异步:在新线程中执行任务,具备开启新线程的能力
dispatch_async
串行队列:在队列里的多个任务按先后顺序,一个一个取出。
dispatch_queue_t serialQueue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
并发队列:在队列里的多个任务允许同时取出。
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
主队列:主线程队列,属于串行队列,放入该队列的任务,在主线程执行。
dispatch_queue_t mainQueue = dispatch_get_main_queue();
全局并发队列:属于并发队列的一种,可全局获得。
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
同步
1.同步串行队列
dispatch_sync(serialQueue, ^{
NSLog(@"block0");
});
dispatch_sync(serialQueue, ^{
NSLog(@"block1");
});
下面代码会产生死锁,serialQueue 串行队列要求一个一个执行,block0 执行完之后才会执行block1,但block1的执行包含在block0里边,造成互相等待。block0执行完的前提是执行block1,但block1执行的前提是先执行完block0。
dispatch_sync(serialQueue, ^{//block0
NSLog(@"block0");
dispatch_sync(serialQueue, ^{//block1
NSLog(@"block1");
});
});
下面代码同样会产生死锁,主队列内放置着一个一个待执行的任务,并在主线程按顺序取出执行。dispatch_sync这个代码块就是其中的一个任务,block0是另一个任务。dispatch_sync执行完的前提是执行block0,但block0要想执行需要代码块先执行完毕。所以造成死锁。
//在主线程执行以下代码
dispatch_sync(mainQueue, ^{//block0
NSLog(@"block0");
});
2.同步并发队列
同样是在当前线程执行,但不会产生死锁。并发队列对任务的取出没有顺序上的要求。
dispatch_sync(concurrentQueue, ^{//block0
NSLog(@"block0");
});
dispatch_sync(concurrentQueue, ^{//block1
NSLog(@"block1");
});
dispatch_sync(concurrentQueue, ^{//block0
NSLog(@"block0");
dispatch_sync(concurrentQueue, ^{//block1
NSLog(@"block1");
});
});
异步
1.异步串行队列
异步会开辟一个子线程,如果是串行队列只会开辟一条子线程
dispatch_async(serialQueue, ^{//block0
NSLog(@"block0");
});
dispatch_async(serialQueue, ^{//block1
NSLog(@"block1");
});
开辟一条子线程,任务放在串行队列,但async与sync不同的是不要求任务立即执行,block1可以等block0执行完后再执行,所以不会产生死锁。
dispatch_async(serialQueue, ^{//block0
NSLog(@"block0");
dispatch_async(serialQueue, ^{//block1
NSLog(@"block1");
});
});
mainQueue主队列,不会开辟子线程,在主线程执行,并不会发生死锁。
dispatch_async(mainQueue, ^{//block0
NSLog(@"block0");
});
//不用等async任务执行,就可以直接来到此处
分析下面两种情况是否会造成死锁:
- 情形一:
dispatch_sync(serialQueue, ^{//block0 NSLog(@"block0"); dispatch_async(serialQueue, ^{//block1 NSLog(@"block1"); }); //不用等async任务执行,就可以直接来到此处 });
- 情形二:
dispatch_async(serialQueue, ^{//block0 NSLog(@"block0"); //同步任务,必须执行 dispatch_sync(serialQueue, ^{//block1 NSLog(@"block1"); }); });
根据上面的分析,情形一不会死锁,情形二会发生死锁。
2.异步并发队列
dispatch_async(concurrentQueue, ^{//block0
NSLog(@"block0");
});
dispatch_async(concurrentQueue, ^{//block1
NSLog(@"block1");
});
dispatch_async(concurrentQueue, ^{//block0
NSLog(@"block0");
dispatch_async(concurrentQueue, ^{//block1
NSLog(@"block1");
});
});
线程组
-(void)GCD_GroupTest{
//全局队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
//创建线程组
dispatch_group_t group = dispatch_group_create();
//添加任务
dispatch_group_async(group, globalQueue, ^{
NSLog(@"block0-%@",[NSThread currentThread]);
});
dispatch_group_async(group, globalQueue, ^{
NSLog(@"block1-%@",[NSThread currentThread]);
});
//等待通知
dispatch_group_notify(group, globalQueue, ^{
NSLog(@"block2-%@",[NSThread currentThread]);
});
dispatch_group_notify(group, globalQueue, ^{
NSLog(@"block3-%@",[NSThread currentThread]);
});
}
- (void)groupDemo2{
// 问题: 如果 dispatch_group_enter 多 dispatch_group_leave 不会调用通知
// dispatch_group_enter 少 dispatch_group_leave 奔溃
// 成对存在
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"第一个走完了");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"第二个走完了");
dispatch_group_leave(group);
});
//如果没有 dispatch_group_leave(group); 就不会来到dispatch_group_notify
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有任务完成,可以更新UI");
});
}
其它
1.一次执行代码
-(void)GCD_Once{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"lilog - once");
});
}
2.延迟执行
-(void)GCD_After{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"大牛");
});
NSLog(@"IT民工");
}
3.实现加锁
//利用GCD的串行队列 来实现加锁
-(void)GCD_SerialQueue_Lock{
__weak typeof(self) weakSelf = self;
//创建一个串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
[weakSelf.task freeMoney:10];
});
dispatch_async(serialQueue, ^{
[weakSelf.task drawMoney:10];
});
}
barrier
需求:有4个任务{1,2,3,4},执行完前2个再执行后2个
这里我们用到栅栏函数dispatch_barrier_(a)sync,(也可以用队列组),我们要注意的是不能使用全局并发队列(系统提供给我们的)否则会散失栅栏函数的意义
区别:先看官方文档
dispatch_barrier_sync: Submits a barrier block object for execution and waits until that block completes.(提交一个栅栏函数在执行中,它会等待栅栏函数执行完)
dispatch_barrier_async: Submits a barrier block for asynchronous execution and returns immediately.(提交一个栅栏函数在异步执行中,它会立马返回)
作者理解:dispatch_barrier_sync 需要等待栅栏执行完才会执行栅栏后面的任务,而dispatch_barrier_async 无需等待栅栏执行完,会继续往下走(保留在队列里)
原因:在同步栅栏时栅栏函数在主线程中执行,而异步栅栏中开辟了子线程栅栏函数在子线程中执行
一、先看代码dispatch_barrier_sync
-(void)barrierSync{
NSLog(@"[lilog]:start");
dispatch_queue_t queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"[lilog]:task1");
});
dispatch_async(queue, ^{
NSLog(@"[lilog]:task2");
});
dispatch_barrier_sync(queue, ^{
NSLog(@"[lilog]:barrier_sync");
});
dispatch_async(queue, ^{
NSLog(@"[lilog]:task3");
});
dispatch_async(queue, ^{
NSLog(@"[lilog]:task4");
});
NSLog(@"[lilog]:last");
}
打印:
[lilog]:start
[lilog]:task1
[lilog]:task2
[lilog]:barrier_sync
[lilog]:last
[lilog]:task3
[lilog]:task4
再次打印
[lilog]:start
[lilog]:task1
[lilog]:task2
[lilog]:barrier_sync
[lilog]:last
[lilog]:task4
[lilog]:task3
二、看看dispatch_barrier_async
-(void)barrierAsync{
NSLog(@"[lilog]:start");
dispatch_queue_t queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"[lilog]:task1");
});
dispatch_async(queue, ^{
NSLog(@"[lilog]:task2");
});
dispatch_barrier_async(queue, ^{
NSLog(@"[lilog]:barrier_async");
});
dispatch_async(queue, ^{
NSLog(@"[lilog]:task3");
});
dispatch_async(queue, ^{
NSLog(@"[lilog]:task4");
});
NSLog(@"[lilog]:last");
}
打印:
[lilog]:start
[lilog]:last
[lilog]:task1
[lilog]:task2
[lilog]:barrier_async
[lilog]:task3
[lilog]:task4
再次打印
[lilog]:start
[lilog]:task1
[lilog]:last
[lilog]:task2
[lilog]:barrier_async
[lilog]:task3
[lilog]:task4
栅栏的作用:
1、同步,调整多线程的调用顺序
2、加锁,栅栏块的block是线程安全的,不会被多线程调用。
3、栅栏块之前添加的任务未执行完不会执行栅栏块的任务。栅栏块其后添加的任务,在栅栏块任务执行完之前,永远得不到执行。
三、特别注意
The queue you specify should be a concurrent queue that you create yourself using the dispatch_queue_create function. If the queue you pass to this function is a serial queue or one of the global concurrent queues, this function behaves like the dispatch_sync function.
官方说明大意:在使用栅栏函数时.使用自定义队列才有意义,如果用的是串行队列或者系统提供的全局并发队列,这个栅栏函数的作用等同于一个同步函数的作用
dispatch_barrier_sync 等同于 dispatch_sync
dispatch_barrier_async 等同于 dispatch_async
semaphore
semaphore叫做”信号量”
信号量的初始值,可以用来控制线程并发访问的最大数量
信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步
//信号量的初始值
int value = 1;
//初始化信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(value);
//如果信号量的值<=0,当前线程就会进入休眠等待(直到信号量>0)
//如果信号量的值>0,就减1,然后往下执行后面的代码。
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
//让信号量的值加1
dispatch_semaphore_signal(semaphore);
-(void)semaphoreTest{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 信号量 -- gcd控制并发数
//总结:由于设定的信号值为2,先执行三个线程,等执行完一个,才会继续执行下一个,保证同一时间执行的线程数不超过2
//如果信号量为1,则可实现加锁,同步执行
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
//任务1
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务1");
sleep(1);
NSLog(@"任务1完成");
dispatch_semaphore_signal(semaphore);
});
//任务2
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务2");
sleep(1);
NSLog(@"任务2完成");
dispatch_semaphore_signal(semaphore);
});
//任务3
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务3");
sleep(1);
NSLog(@"任务3完成");
dispatch_semaphore_signal(semaphore);
});
}
dispatch_source
- (void)viewDidLoad {
[super viewDidLoad];
self.queue = dispatch_queue_create("xxxx", 0);
/**
第一个参数:dispatch_source_type_t type为设置GCD源方法的类型,前面已经列举过了。
第二个参数:uintptr_t handle Apple的API介绍说,暂时没有使用,传0即可。
第三个参数:unsigned long mask Apple的API介绍说,使用DISPATCH_TIMER_STRICT,会引起电量消耗加剧,毕竟要求精确时间,所以一般传0即可,视业务情况而定。
第四个参数:dispatch_queue_t _Nullable queue 队列,将定时器事件处理的Block提交到哪个队列之上。可以传Null,默认为全局队列。注意:当提交到全局队列的时候,时间处理的回调内,需要异步获取UI线程,更新UI...不过这好像是常识,又啰嗦了...
*/
self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
// 保存代码块 ---> 异步 dispatch_source_set_event_handler()
// 设置取消回调 dispatch_source_set_cancel_handler(dispatch_source_t source,dispatch_block_t _Nullable handler)
// 封装我们需要回调的触发函数 -- 响应
dispatch_source_set_event_handler(self.source, ^{
NSUInteger value = dispatch_source_get_data(self.source); // 取回来值 1 响应式
});
dispatch_resume(self.source);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
for (NSUInteger index = 0; index < 100; index++) {
dispatch_async(self.queue, ^{
sleep(2);
dispatch_source_merge_data(self.source, 1); // source 值响应
});
}
}
行者常至,为者常成!