GCD学习
- GCD其他方法
-
-
- [dispatch_semaphore (信号量)](#dispatch_semaphore (信号量))
- **什么是信号量**
- dispatch_semaphore主要作用
-
- dispatch_semaphore主要作用
- [dispatch_time_t 两种形式](#dispatch_time_t 两种形式)
- GCD一次性代码(只执行一次)
- dispatch_barrier_async/sync栅栏方法
-
GCD其他方法
前面对GCD进行了简单的学习,这里笔者对一些容易遗忘和重要的方法再次学习。
dispatch_semaphore (信号量)
什么是信号量
引用学长的解释
以一个停车场的运作为例。简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看 门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开 车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用。
信号量主要用作同步锁,用于控制GCD最大并发数
主要涉及以下三个函数:
objectivec
// 创建信号量,value:初始信号量数 如果小于0则会返回NULL
dispatch_semaphore_create(long value);
// 发送信号量是的信号量+1
dispatch_semaphore_signal(dispatch_semaphore_t deem);
//可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
注意:信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量。
dispatch_semaphore主要作用
dispatch_semaphore主要作用
- 保持线程同步,将异步执行任务转换为同步执行任务
- 保证线程安全,为线程加锁
异步转同步
objectivec
- (void)cjl_testSemaphore1{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
//创建异步队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
sleep(1);
NSLog(@"执行任务A");
//信号量+1
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
sleep(1);
NSLog(@"执行任务B");
//信号量+1,相当于解锁
dispatch_semaphore_signal(semaphore);
});
//当当前的信号量值为0时候会阻塞线,如果大于0的话,信号量-1,不阻塞线程
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
sleep(1);
NSLog(@"执行任务C");
//信号量+1,相当于解锁
dispatch_semaphore_signal(semaphore);
});
}
多次运行的结果都是A,B,C顺序执行,让A,B,C异步执行变成同步执行,dispatch_semaphore相当于加锁效果
设置一个最大开辟的线程数
objectivec
- (void)cjl_testSemaphore{
//设置最大开辟的线程数为3
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
//创建一个并发队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//开启的是10个异步的操作,通过信号量,让10个异步的最多3个m,剩下的同步等待
for (NSInteger i = 0; i < 10; i++) {
dispatch_async(queue, ^{
//当信号量为0时候,阻塞当前线程
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务 %ld", i);
sleep(2);
NSLog(@"完成当前任务 %ld", i);
//释放信号
dispatch_semaphore_signal(semaphore);
});
}
}

但如果sleep时间变成1,则会出现以下结果:
这是因为出现了优先级反转的问题。
先介绍什么是优先级反转。
在程序执行时多个任务可能会对同---份数据产生 竞争'因此任务会使用锁来保护共享数据°假设现在有3个任务A、B`C'它们的优先 级为A>B>C任务C在运行时持有一把锁'然后它被高优先级的任务A抢占了(任 务C的锁没有被释放)。此时任务A恰巧也想申请任务C持有的锁'但是申请失败,因 此进人阻塞状态等待任务C放锁。此时'任务B、C都处于可以运行的状态,由于任务 B的优先级高于C,因此B优先运行°综合观察该情况'就会发现任务B好像优先级高 于任务A'先于任务A执行。

信号量调度是公平队列(FIFO/不考虑 QoS)+ 系统线程调度是根据优先级(QoS)来的,两者机制不一致,所以可能造成高优先级线程因为信号量资源被低优先级线程占用而"被阻塞"。
加锁机制
- 当你的信号设置为1的时候就相当于加锁
objectivec
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"semaphore---begin");
semaphoreLock = dispatch_semaphore_create(1);
self.ticketCount = 50;
// queue1 代表北京火车票售卖窗口
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
// queue2 代表上海火车票售卖窗口
dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf saleTicketSafe];
});
dispatch_async(queue2, ^{
[weakSelf saleTicketSafe];
});
}
- (void)saleTicketSafe {
while (1) {
// 相当于加锁
dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
if (self.ticketCount > 0) {
//如果还有票,继续售卖
self.ticketSurplusCount--;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
[NSThread sleepForTimeInterval:0.2];
} else {
//如果已卖完,关闭售票窗口
NSLog(@"所有火车票均已售完");
// 相当于解锁
dispatch_semaphore_signal(semaphoreLock);
break;
}
// 相当于解锁
dispatch_semaphore_signal(semaphoreLock);
}
}
dispatch_time_t 两种形式
- 基于
dispatch_time
函数,表示从当前时间开始的一个时间点
objectivec
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);
使用:
objectivec
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(60 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"dispatch_time");
});
时间单位:
objectivec
#define NSEC_PER_SEC 1000000000ull 1秒
#define NSEC_PER_MSEC 1000000ull 1毫秒
#define USEC_PER_SEC 1000000ull 1秒
#define NSEC_PER_USEC 1000ull 1微秒
时间点:
#define DISPATCH_TIME_NOW (0ull) 0->现在
#define DISPATCH_TIME_FOREVER (~0ull) -1->永远
dispatch_time_t定义
typedef uint64_t dispatch_time_t; unsigned long long 64位无符号长整形
GCD一次性代码(只执行一次)
使用dispatch_once
方法能保证某段代码在程序运行过程中只执被执行1次,即使在多线程的环境下,该方法也可以保证建成安全。之前在创建单例模式时就接触过该方法。
objectivec
- (void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行 1 次的代码(这里面默认是线程安全的)
});
}
dispatch_barrier_async/sync栅栏方法
在访问数据库或者文件的时候,我们可以使用Serial Dispatch Queue可避免数据竞争问题。即多读单写
objectivec
- (void)cjl_testBarrier{
/*
dispatch_barrier_sync & dispatch_barrier_async
应用场景:同步锁
等栅栏前追加到队列中的任务执行完毕后,再将栅栏后的任务追加到队列中。
简而言之,就是先执行栅栏前任务,再执行栅栏任务,最后执行栅栏后任务
- dispatch_barrier_async:前面的任务执行完毕才会来到这里
- dispatch_barrier_sync:作用相同,但是这个会堵塞线程,影响后面的任务执行
- dispatch_barrier_async可以控制队列中任务的执行顺序,
- 而dispatch_barrier_sync不仅阻塞了队列的执行,也阻塞了线程的执行(尽量少用)
*/
[self cjl_testBarrier1];
[self cjl_testBarrier2];
}
- (void)cjl_testBarrier1{
//串行队列使用栅栏函数
dispatch_queue_t queue = dispatch_queue_create("CJL", DISPATCH_QUEUE_SERIAL);
NSLog(@"开始 - %@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"延迟2s的任务1 - %@", [NSThread currentThread]);
});
NSLog(@"第一次结束 - %@", [NSThread currentThread]);
//栅栏函数的作用是将队列中的任务进行分组,所以我们只要关注任务1、任务2
dispatch_barrier_async(queue, ^{
NSLog(@"------------栅栏任务------------%@", [NSThread currentThread]);
});
NSLog(@"栅栏结束 - %@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"延迟2s的任务2 - %@", [NSThread currentThread]);
});
NSLog(@"第二次结束 - %@", [NSThread currentThread]);
}
- (void)cjl_testBarrier2{
//并发队列使用栅栏函数
dispatch_queue_t queue = dispatch_queue_create("CJL", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"开始 - %@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"延迟2s的任务1 - %@", [NSThread currentThread]);
});
NSLog(@"第一次结束 - %@", [NSThread currentThread]);
//由于并发队列异步执行任务是乱序执行完毕的,所以使用栅栏函数可以很好的控制队列内任务执行的顺序
dispatch_barrier_async(queue, ^{
NSLog(@"------------栅栏任务------------%@", [NSThread currentThread]);
});
NSLog(@"栅栏结束 - %@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"延迟2s的任务2 - %@", [NSThread currentThread]);
});
NSLog(@"第二次结束 - %@", [NSThread currentThread]);
}
重点观察并发队列中使用栅栏函数。会在栅栏前的任务完成后,才执行栅栏后的任务。

dispatch_barrier_sync
- 会阻塞当前线程,看打印结果可知,"start"一定在dispatch_barrier_sync函数前执行,"end"一定在dispatch_barrier_sync函数后执行
- dispatch_barrier_sync 函数添加的block,在当前线程执行
- 会将传入dispatch_barrier_sync函数的线程myQueue中的任务,通过dispatch_barrier_sync函数位置把前后分隔开。
dispatch_barrier_async
- 不会阻塞当前线程,"start","end"与dispatch_barrier_async方法是同步执行的,执行顺序不定。
- dispatch_barrier_sync函数添加的block,在新开的线程执行
- 会将传入dispatch_barrier_async函数的线程myQueue中的任务,通过dispatch_barrier_async函数位置把前后分隔开。
这也是为什么栅栏结束会出现在栅栏任务之前。