GCD(Grand Central Dispatch) 是基于C语言的API,是苹果公司为多核的并行运算提出的解决方案。GCD会自动利用更多的CPU内核(比如双核、四核)。程序员只需要将任务添加到队列中,并且指定执行任务的函数,不需要别写任何线程管理的代码。
学习 GCD 之前,先来了解 GCD 中两个核心概念:任务
和 队列
。
一. 任务 - Task
任务
:就是执行操作的意思,通俗的说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。
执行任务有两种方式:同步执行
和 异步执行
。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力
。
- 同步任务
dispatch_sync
- 同步添加任务到指定的队列中,必须
等待
当前队列的任务执行完毕,才能执行下一个任务 - 只能在当前线程中执行任务,不具备开启新线程的能力。
- 异步任务
dispatch_async
- 异步添加任务到指定的队列中,它不会做任何
等待
,可以继续执行任务。 - 可以在新的线程中执行任务,具备开启新线程的能力
- 注意:异步执行虽然具有开启新线程的能力,但是并不一定开启新线程。这跟任务所指定的队列类型有关(下面会讲)
二. 队列 - Dispatch Queue
队列(Dispatch Queue)
:这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(First - In -First - Out)
的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。队列的结构可参考下图:
在 GCD 中有两种队列:串行队列
和 并发队列
。两者都符合 FIFO
原则。两者的主要区别是:执行顺序不同,以及开启线程数不同
。
- 串行队列
Serial Dispatch Queue
每次只有一个任务
被执行,只申请一个线程
,一个任务
执行完毕,再执行下一个
Concurrent Dispatch Queue
可以申请多个 线程
,让多个 任务
同时执行(注意:并发队列的并发功能只有在 异步dispatch_async 方法下有效)
三. GCD的使用
GCD使用很简单,只有三步
- 创建
任务
(同步任务 或者 异步任务) - 创建
队列
(串行队列 或者 并发队列) - 将
任务
追加到任务的等待队列
中,然后系统就会根据任务类型执行任务(同步执行 或者 异步执行)
3.1 任务的创建
同步执行任务的创建方法 dispatch_sync
和 异步执行任务的创建方法 dispatch_async
。
// 同步任务创建方法
dispatch_sync(queue, ^{
// 这里放同步任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
// 这里放异步任务代码
});
3.2 队列的创建
GCD 可以使用 dispatch_queue_creat
创建队列
dispatch_queue_t queue - dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t _Nullable attr#>);
const char * _Nullable label
: 队列的唯一标识符,用于debug,可以为空。dispatch_queue_attr_t _Nullable attr
: 队列种类。DISPATCH_QUEUE_SERIAL
串行队列,DISPATCH_QUEUE_CONCURRENT
并发队列。
第二个参数如果传NULL,会创建串行队列。
主队列
对于串行队列,GCD默认提供了:主队列 - Main Dispatch Queue
- 所有放在主队列的任务,都会放在主线程执行
- 可以使用
dispatch_get_main_queue()
方法获得主队列
主队列本质就是一个普通的串行队列,只是默认情况下,代码放在主队列中,主队列的代码会放到主程序中执行,给我们主队列特殊的感觉。
全局并发队列
对于并发队列,GCD默认提供了:全局并发队列 - Global Dispatch Queue
。可以使用 dispatch_get_global_queue()
方法获得全局并发队列
#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_queue_t queue = dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>)
long identifier
: 标示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT
- 暂时没用,可以传0
4.任务 和 队列 不同组合方式
4.1 同步任务 + 串行队列
- (void)syncSerial
{
NSLog(@"主线程 - %@", [NSThread currentThread]);
NSLog(@"---begin---");
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_SERIAL);
for (NSInteger i = 0; i < 10; i ++) {
dispatch_sync(queue, ^{
NSLog(@"同步任务 + 串行队列:%ld -- %@", (long)i, [NSThread currentThread]);
});
}
NSLog(@"---end---");
}
- 所有任务都在当前线程(主线程)中执行,没有开启新的线程。(
同步任务只能在当前线程中执行任务,不具备开启新线程的能力
) - 所有打印都在 ---begin--- 和 ---end--- 之间执行(
同步任务需要等待队列的任务执行结束
) - 任务按顺序执行(
串行队列每次只有一个任务被执行,任务一个接一个按顺序执行
)。
4.2 异步任务 + 串行队列
- (void)asyncSerial
{
NSLog(@"主线程 - %@", [NSThread currentThread]);
NSLog(@"---begin---");
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_SERIAL);
for (NSInteger i = 0; i < 10; i ++) {
dispatch_async(queue, ^{
NSLog(@"异步任务 + 串行队列:%ld -- %@", (long)i, [NSThread currentThread]);
});
}
NSLog(@"---end---");
}
- 开启了一条新线程(
异步任务具有开辟线程的能力,串行队列只开启一个线程
) - 所有任务在 ---begin--- 和 ---end--- 之后执行(
异步执行不会做任何等待,可以继续执行接下来的任务
) - 任务按顺序执行(
串行队列每次只有一个任务被执行,任务一个接一个按顺序执行
)
4.3 同步任务 + 并行队列
- (void)syncConcurrent
{
NSLog(@"主线程 - %@", [NSThread currentThread]);
NSLog(@"---begin---");
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i < 10; i ++) {
dispatch_sync(queue, ^{
NSLog(@"同步任务 + 并行队列:%ld -- %@", (long)i, [NSThread currentThread]);
});
}
NSLog(@"---end---");
}
- 所有任务都在当前线程(主线程)中执行,没有开辟细腻的线程(
同步任务只能在当前线程中执行任务,不具备开启新线程的能力
) - 所有打印都在 ---begin--- 和 ---end--- 之间执行(
同步任务需要等待队列的任务执行结束
) - 任务按顺序执行:原因:虽然
并发队列
可以开启多个线程,并且同时执行多个任务。但是因为本身不能创建新线程,只有当前线程这一个线程(同步任务不具备开启新线程的能力
),所以也就不存在并发。而且当前线程只有等待当前队列中正在执行的任务执行完毕之后,才能继续接着执行下面的操作(同步任务 需要等待队列的任务执行结束
)。所以任务只能一个接一个按顺序执行,不能同时被执行。
4.4 异步任务 + 并行队列
- (void)asyncConcurrent
{
NSLog(@"主线程 - %@", [NSThread currentThread]);
NSLog(@"---begin---");
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i < 10; i ++) {
dispatch_async(queue, ^{
NSLog(@"异步任务 + 并行队列:%ld -- %@", (long)i, [NSThread currentThread]);
});
}
NSLog(@"---end---");
}
- 除了当前线程(主线程),系统又开启了多 个线程,并且任务是交替/同时执行的。(
异步任务具备开启新线程的能力。 并发队列 可开启多个线程,同时执行多个任务
)。 - 所有任务在 ---begin--- 和 ---end--- 之后执行(
异步执行不会做任何等待,可以继续执行接下来的任务
)
4.5 同步任务 + 主队列
同步任务 + 主队列
在不同线程中调用结果也是不一样,在主线程中调用会发生死锁问题,而在其他线程中调用则不会。
- 主线程调用
同步任务 + 主队列
崩溃原因:主队列是串行队列 所有放在主队列中的任务,都会放到主线程中执行
, 当我们在主线程
中执行syncMain方法,相当于把 syncMain任务
放到了 主线程
的队列中。而 同步执行会等待当前队列中的任务执行完毕,才会接着执行
。那么当我们把 任务1
追加到主队列中,任务1
就在等待主线程处理完 syncMain任务
。而 syncMain任务
需要等待 任务1
执行完毕,才能接着执行。两个任务 相互等待
对方执行完毕。
- 子线程调用
同步任务 + 主队列
// 使用 NSThread 的 detachNewThreadSelector 方法会创建线程,并自动启动线程执行 selector 任务
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];
- 所有任务都是在主线程(非当前线程)中执行的,没有开启新的线程(
所有放在主队列中的任务,都会放到主线程中执行
) 所有打印都在 ---begin--- 和 ---end--- 之间执行(同步任务需要等待队列的任务执行结束
) - 任务是按顺序执行的(
主队列是 串行队列,每次只有一个任务被执行,任务一个接一个按顺序执行
)
为什么现在就不会卡住了呢?
因为 syncMain任务
放到 子线程
里; 而 任务1到10
都在追加到主队列中,会在 主线程
中执行。主队列现在没有正在执行的任务,所以会直接执行主队列的 任务1
,等 任务1
执行完毕,再接着执行 任务2
。所以这里不会卡住线程,也就不会造成死锁问题。
4.6 嵌套使用
以下demo默认都在主队列,因为主队列是串行队列,代码自上到下依次执行。默认异步操作都是长耗时操作。
- 串行异步中嵌套同步
- (void)demo1
{
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(queue, ^{
sleep(2);
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
打印顺序:1 5 2 死锁
代码块B
为同步任务,等待,产生阻塞。所以,任务4
要等到代码块B
执行完毕才能执行。代码块B
要执行完毕,必须等到任务3
执行完毕.- 因为是串行队列,遵循FIFO。
任务3
要等到任务4
执行完毕才执行 - 所以,
任务4
等块B
,块B
等任务3
,任务3
等任务4
。死锁。
- 串行同步中嵌套异步
- (void)demo2
{
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_sync(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
sleep(2);
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
打印顺序: 12453
- 主队列,代码依次执行:队列中依次加入
任务1 代码块A 任务5
。代码块A
是同步任务,等待。 所以打印 1 代码块A 5 代码块A
内: 队列中依次加入任务2 代码块B 任务4
。代码块B
是异步任务,不等待,耗时。所以打印 2 4 3代码块B
执行完毕后 打印5- 同步操作保证了 4一定在5之前打印
- 并行异步中嵌套同步
- (void)demo3
{
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
sleep(2);
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
打印结果:15234
- 主队列,代码依次执行:队列中依次加入
任务1 代码块A 任务5
。代码块A
是异步任务,不等待,耗时。所以打印 1 5 代码块A
内:并行队列,申请新线程。新线程依次加入任务2 代码块B 任务4
。代码块B
同步任务,等待阻塞当前子线程。打印 2 3 4- 同步操作保证了 3 一定在 4 之前打印
- 并行同步中嵌套异步
- (void)demo4
{
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_sync(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
sleep(2);
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
打印结果:12453
- 主队列,代码依次执行:队列中依次加入
任务1 代码块A 任务5
。代码块A
是同步任务,等待。所以打印 1 代码块A 5 代码块A
内:并行队列,加入任务2 代码块B 任务4
。代码块B
异步任务,不等待,耗时,申请新的线程。- 同步操作保证 4 一定在 5 之前打印
5. GCD的应用
5.1 dispatch_after 延时执行方法
应用场景:在指定时间之后执行某个任务。
dispatch_after
方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after
方法是很有效的。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2秒后输出");
});
5.2 dispatch_once 一次性代码
应用场景:单例,method-Swizzling
dispatch_once
能保证某段代码在程序运行过程中只被执行 1 次,并且即使在多线程的环境下,dispatch_once
也可以保证线程安全。
- (void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行 1 次的代码(这里面默认是线程安全的)
});
}
5.3 dispatch_apply 快速迭代
应用场景:获得网络数据后提前算出各个控件的大小
dispatch_apply 按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。
- 串行队列中使用 dispatch_apply,那么就和 for 循环一样,按顺序同步执行。但是这样就体现不出快速迭代的意义了。
- 我们可以利用并发队列进行异步执行。比如说遍历 0~5 这 6 个数字,for 循环的做法是每次取出一个元素,逐个遍历。dispatch_apply 可以 在多个线程中同时(异步)遍历多个数字。
- (void)apply
{
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_SERIAL);
NSLog(@"dispatch_apply前");
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"dispatch_apply -- 线程%zu-%@", index, [NSThread currentThread]);
});
NSLog(@"dispatch_apply后");
}
无论是在串行队列,还是并发队列中,dispatch_apply 都会等待全部任务执行完毕, ,这点就像是同步操作
,也像是队列组中的 dispatch_group_wait
方法。
5.4 dispatch_barrier 栅栏函数
应用场景:线程同步
异步任务+并发队列会开辟线程,多个任务同时进行,各任务也会因为任务复杂度和cpu的调度导致执行完毕乱序。如何设置让任务顺序执行完毕,栅栏函数就是很好的解决办法。
-
dispatch_barrier_async
用于提交异步执行栅栏函数块任务,并立即返回。该函数不会阻塞线程
,只会阻塞任务
执行。栅栏函数提交之后并不会立即执行,而是会直接返回。等待在栅栏函数之前提交的任务都执行完成之后,栅栏函数任务会自动执行,在栅栏函数之后提交的任务必须在栅栏函数执行完成之后才会执行. -
dispatch_barrier_sync
用于提交同步执行栅栏函数块任务,并不会立即返回,需要等待栅栏函数提交的任务执行完毕之后才会返回。与dispatch_barrier_async不同,由于该函数需要同步执行,在该栅栏函数任务执行完成之前,函数不会返回,所以此时后边的任务都会被阻塞
.
- 测试1:
- (void)barrierSync
{
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"begin -- %@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"任务1 -- %@", [NSThread currentThread]);
});
dispatch_barrier_sync(queue, ^{
NSLog(@"dispatch_barrier_sync -- %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务2 -- %@", [NSThread currentThread]);
});
NSLog(@"end -- %@", [NSThread currentThread]);
}
- 测试2
- (void)barrierAsync
{
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"begin -- %@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"任务1 -- %@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"dispatch_barrier_async -- %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务2 -- %@", [NSThread currentThread]);
});
NSLog(@"end -- %@", [NSThread currentThread]);
}
比较 测试1
和 测试2
:
dispatch_barrier_sync
与dispatch_barrier_async
的共同点:
- 都会等待在它前面插入队列的任务(
任务begin
任务1
)先执行完。 - 都会等待他们自己的任务(
barrier operation
)执行完之后再执行后面的任务(任务2
任务end
)。
dispatch_barrier_sync
与dispatch_barrier_async
的不同点:
- 将任务插入到队列时,
dispatch_barrier_sync
需要等待自己的任务(barrier operation
)执行完毕后,才会插入后面的任务(任务2
任务end
),然后执行后面的任务。所以任务end
比barrier operation
后打印。说白了,会阻塞线程。 dispatch_barrier_async
将自己的任务插入到队列之后,不会等待自己的任务(barrier operation)执行结果,它会继续插入后面的任务。所以任务end
先打印。
- 测试3:
dispatch_barrier_sync线程
与任务线程
不一致,会怎样?
- (void)barrierSync
{
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_CONCURRENT);
// 新建线程
dispatch_queue_t queue2 = dispatch_queue_create("com.akironer2", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"begin -- %@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"任务1 -- %@", [NSThread currentThread]);
});
// 阻塞新建线程
dispatch_barrier_sync(queue2, ^{
NSLog(@"dispatch_barrier_sync -- %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务2 -- %@", [NSThread currentThread]);
});
NSLog(@"end -- %@", [NSThread currentThread]);
}
使用 dispatch_barrier_async
测试,也有同样的结果
- barrier的本质是阻塞线程。
dispatch_barrier 线程
与任务线程
不一致,barrier没有发挥作用。 - 所以我们使用AFNetworking做网络请求时,不能用栅栏函数起到同步锁堵塞的效果,因为AFNetworking内部有自己的队列。
- 测试4:对
全局并发队列
使用栅栏函数,有什么效果?
- 对全局并发队列使用栅栏函数, 可能致使系统其他调用全局队列的地方也堵塞从而导致崩溃。
5.5 dispatch_group 调度组
应用场景:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后再回到主线程执行任务。
5.5.1 dispatch_group_async & dispatch_group_notify
dispatch_group_async
先把任务放到队列中,然后将队列放入队列组中dispatch_group_notify
监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务
到group
中,并执行任务。
- (void)groupAsync
{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"begin -- %@", [NSThread currentThread]);
dispatch_group_async(group, queue, ^{
sleep(5);
NSLog(@"任务1 -- %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
sleep(3);
NSLog(@"任务2 -- %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
sleep(4);
NSLog(@"任务3 -- %@", [NSThread currentThread]);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"end -- %@", [NSThread currentThread]);
});
}
打印:
22:23:12.180761+0800 GCD[2123:330417] begin -- <NSThread: 0x60000212ad40>{number = 1, name = main}
22:23:15.182107+0800 GCD[2123:330584] 任务2 -- <NSThread: 0x600002150080>{number = 3, name = (null)}
22:23:16.180985+0800 GCD[2123:330585] 任务3 -- <NSThread: 0x60000215c180>{number = 4, name = (null)}
22:23:17.182406+0800 GCD[2123:330596] 任务1 -- <NSThread: 0x600002122ec0>{number = 5, name = (null)}
22:23:17.182667+0800 GCD[2123:330596] end -- <NSThread: 0x600002122ec0>{number = 5, name = (null)}
5.5.2 dispatch_group_wait
暂停当前线程,等待指定的group中的任务执行完成之后,才往下执行任务。相比于dispatch_group_notify
, dispatch_group_wait
会阻塞线程。
- (void)groupWait
{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"begin -- %@", [NSThread currentThread]);
dispatch_group_async(group, queue, ^{
sleep(5);
NSLog(@"任务1 -- %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
sleep(3);
NSLog(@"任务2 -- %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
sleep(4);
NSLog(@"任务3 -- %@", [NSThread currentThread]);
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"end -- %@", [NSThread currentThread]);
}
打印:
22:31:45.390633+0800 GCD[2157:336097] begin -- <NSThread: 0x6000001d6d00>{number = 1, name = main}
22:31:48.392504+0800 GCD[2157:336257] 任务2 -- <NSThread: 0x6000001a4640>{number = 6, name = (null)}
22:31:49.392455+0800 GCD[2157:336255] 任务3 -- <NSThread: 0x6000001dc400>{number = 5, name = (null)}
22:31:50.395577+0800 GCD[2157:336256] 任务1 -- <NSThread: 0x6000001ac380>{number = 4, name = (null)}
22:31:50.395978+0800 GCD[2157:336256] end -- <NSThread: 0x6000001ac380>{number = 4, name = (null)}
5.5.3 dispatch_group_enter & dispatch_group_leave
dispatch_group_enter
标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1dispatch_group_leave
标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1。- 当 group 中未执行完毕任务数为0的时候,才会使
dispatch_group_wait
解除阻塞,以及执行追加到dispatch_group_notify
中的任务。
- (void)groupWait
{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"begin -- %@", [NSThread currentThread]);
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
sleep(5);
NSLog(@"任务1 -- %@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
sleep(3);
NSLog(@"任务2 -- %@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"end -- %@", [NSThread currentThread]);
});
}
打印:
22:42:10.338809+0800 GCD[2188:343124] begin -- <NSThread: 0x60000338adc0>{number = 1, name = main}
22:42:13.340545+0800 GCD[2188:343288] 任务2 -- <NSThread: 0x6000033e8340>{number = 5, name = (null)}
22:42:15.343788+0800 GCD[2188:343293] 任务1 -- <NSThread: 0x600003304000>{number = 7, name = (null)}
22:42:15.344152+0800 GCD[2188:343293] end -- <NSThread: 0x600003304000>{number = 7, name = (null)}
5.6 dispatch_semaphore 信号量
应用场景:
- 保持线程同步,将异步执行任务转化为同步执行任务
- 保证线程安全,为线程加锁。
信号量是基于计数器的一种多线程同步机制,用来管理对资源的并发访问。信号量内部有一个可以原子递增或递减的值。如果一个动作尝试减少信号量的值,使其小于0,那么这个动作将会被阻塞,直到有其他调用者(在其他线程中)增加该信号量的值。
dispatch_semaphore_create
:创建一个 Semaphore 并初始化信号的总量dispatch_semaphore_signal
:发送一个信号,让信号总量加 1dispatch_semaphore_wait
:可以使总信号量减 1。信号量小于等于 0 则会阻塞当前线程,直到信号量大于0或者经过输入的时间值;若信号量大于0,则会使信号量减1并返回,程序继续住下执行。
在 dispatch_semaphore_wait
和 dispatch_semaphore_signal
这两个函数中间的执行代码,每次只会允许限定数量的线程进入,这样就有效的保证了在多线程环境下,只能有限定数量的线程进入。
注意:信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量。
5.6.1 dispatch_semaphore 线程同步
- (void)semaphoreAync
{
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"%d -- 线程%@", i, [NSThread currentThread]);
// 打印任务结束后信号量解锁
dispatch_semaphore_signal(sem);
});
// 异步耗时,所以这里信号量加锁
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
}
- semaphore 初始创建时计数为 0。
- 异步执行 将
任务1
追加到队列之后,不做等待,接着执行dispatch_semaphore_wait
,semaphore 减 1,此时 semaphore == -1,当前线程进入阻塞状态。 任务1
开始执行。执行到dispatch_semaphore_signal
之后,总信号量加 1,此时 semaphore == 0,正在被阻塞的线程恢复继续执行。
5.6.2 dispatch_semaphore 线程安全
- (void)saleTickets
{
NSLog(@"begin -- %@", [NSThread currentThread]);
self.ticketCount = 20;
// queue1 代表售卖窗口1
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
// queue2 代表售卖窗口2
dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf saleTicketsSemaphore];
});
dispatch_async(queue2, ^{
[weakSelf saleTicketsSemaphore];
});
}
- (void)saleTicketsSemaphore
{
while (1) {
// 相当于加锁
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
if (self.ticketCount > 0) {
self.ticketCount --;
NSLog(@"剩余票数:%ld 窗口:%@", (long)self.ticketCount, [NSThread currentThread]);
sleep(0.1);
} else {
NSLog(@"卖完了%@", [NSThread currentThread]);
// 相当于解锁
dispatch_semaphore_signal(self.semaphore);
break;
}
// 相当于解锁
dispatch_semaphore_signal(self.semaphore);
}
}
5.6.3 dispatch_semaphore 最大并发量
- (void)dispatchAsyncLimit
{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
//任务1
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"任务1执行--%@", [NSThread currentThread]);
sleep(1);
NSLog(@"任务1完成--%@", [NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
//任务2
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"任务2执行--%@", [NSThread currentThread]);
sleep(1);
NSLog(@"任务2完成--%@", [NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
//任务3
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"任务3执行--%@", [NSThread currentThread]);
sleep(1);
NSLog(@"任务3完成--%@", [NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
}
![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/4/26/171b23c361a2d077~tplv-t2oaga2asx-image.image)
由于设定的信号值为2,先执行2个线程,等执行完一个,才会继续执行下一个,保证同一时间执行的线程数不超过2
5.7 dispatch_source
应用场景:GCDTimer
NSTimer是依赖Runloop的,而Runloop可以运行在不同的模式下。如果Runloop在阻塞状态,NSTimer触发时间就会推迟到下一个Runloop周期,因此NSTimer在计时上会有误差。而GCD定时器不依赖Runloop,计时精度要高很多。
// 强持有
@property (nonatomic, strong) dispatch_source_t timer;
// 1.创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2.创建timer
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 3.设置timer首次执行时间,间隔,精确度
dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
// 4.设置timer事件回调
dispatch_source_set_event_handler(_timer, ^{
NSLog(@"GCDTimer");
});
// 5.默认是挂起状态,需要手动激活
dispatch_resume(_timer);