GCD详解二
dispatch_after
第一个参数时指定时间用的dispatch_time_t类型的值。该值用dispatch_time函数或dispatch_walltime函数作成。第二个参数指定要追加处理的Dispatch Queue,第三个参数指定记述要执行处理的Block。
dispatch_time
第一个参数是从什么时间开始,一般直接传 DISPATCH_TIME_NOW,表示从现在开始。第二个参数表示具体的时间长度(不能直接传 int 或 float), 可以写成这种形式 (int64_t)3* NSEC_PER_SEC,"ull"时C语言的数值字面量,是显示表明类型使用的字符串(表示“unsigned long long”)。
NSEC_PER_SEC 1000000000ull 每秒有1000000000纳秒
NSEC_PER_MSEC 1000000ull 每毫秒有1000000纳秒
USEC_PER_SEC 1000000ull 每秒有1000000微秒
NSEC_PER_USEC 1000ull 每微秒有1000纳秒
1秒的写作方式可以是 1* NSEC_PER_SEC; 1000* NSEC_PER_MSEC; USEC_PER_SEC* NSEC_PER_USEC
dispatch_walltime
用于计算绝对时间,例如2018年7月26日11点30分30秒,如果你不需要自某一个特定的时刻开始,可以传 NUll,表示自动获取当前时区的当前时间作为开始时刻,可以作为闹钟功能使用;第二参数意义同dispatch_time。
两者之间本质区别:当device进入休眠之后,系统的时钟也会进入休眠状态, 第一个函数同样被挂起; 假如device在第一个函数开始执行后10分钟进入了休眠状态,那么这个函数同时也会停止执行,当你再次唤醒device之后,该函数同时被唤醒,但是事件的触发就变成了从唤醒device的时刻开始。而第二个函数则不同,他创建的是一个绝对的时间点,一旦创建就表示从这个时间点开始,1小时之后触发事件,假如device休眠了10分钟,当再次唤醒device的时候,计算时间间隔的时间起点还是开始时就设置的那个时间点, 而不会受到device是否进入休眠影响。
注意:dispatch_after函数并不是在指定时间后执行,而是在指定时间追加处理到Dispatch Queue。
dispatch_time_t time = dispatch_time(DISPATCH_TIMER_NOW, 3ull*NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@“test1");
});
NSLog(@“test2”);
输出结果:test2 test1
此源代码与在3秒后用dispatch_async函数追加到Main Dispatch Queue相同。
栅栏(dispatch_barrier)
使用dispatch_barrier_async函数会等待追加到Concurrent Dispatch Queue上的并行执行的处理全部结束之后,再将指定的处理追加到该Concurrent Dispatch Queue中。然后再由dispatch_barrier_async函数追加的处理执行完毕后,Concurrent Dispatch Queue才恢复为一般的动作,追加到该Concurrent Dispatch Queue的处理又开始并行执行。dispatch_barrier_async不会阻塞主线程,dispatch_barrier_sync则会阻塞主线程。
dispatch_queue_t concurrent_queue = dispatch_queue_create("com.myThread.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrent_queue, ^{
NSLog(@"test1");
});
dispatch_async(concurrent_queue, ^{
NSLog(@"test2");
});
dispatch_barrier_async(concurrent_queue, ^{
NSLog(@"添加个栅栏");
});
NSLog(@"test3");
dispatch_async(concurrent_queue, ^{
NSLog(@"test4");
});
dispatch_async(concurrent_queue, ^{
NSLog(@"test5");
});
输出结果为:test3 test2 test1 添加个栅栏 test4 test5
dispatch_apply
dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API。该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"testBegin");
dispatch_apply(5, queue, ^(size_t index) {
NSLog(@"index=%zu",index);
});
NSLog(@"testOver”);
输出结果为:testBegin 0 1 2 3 4 testOver
dispatch_suspend/dispatch_resume
dispatch_suspend函数挂起指定的Dispatch Queue;dispatch_resume函数恢复指定的Dispatch Queue。这些函数对已经执行的处理没有影响。挂起后,追加到Dispatch Queue中但尚未执行的处理,在此之后停止执行。而恢复则使得这些处理能够继续执行。
dispatch_queue_t concurrent_queue = dispatch_queue_create("com.myThread.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrent_queue, ^{
sleep(2);
NSLog(@"test2");
});
dispatch_suspend(concurrent_queue);
NSLog(@"test3");
dispatch_resume(concurrent_queue);
dispatch_async(concurrent_queue, ^{
NSLog(@"test4");
});
输出结果为:test3 test4 test2
信号量(dispatch_semaphore)
1.创建信号量:
Value为可并行执行的最大线程数
dispatch_semaphore_t semaphore = dispatch_semaphore_create(long value);
2.dispatch_semaphore_wait的声明
如果当前dsema信号量的值大于0,该函数所处线程就继续往下执行,并且对传入的信号量值减1;如果为0,那么这个函数就阻塞当前线程等待timeout,当等待期间dsema信号量被dispatch_semaphore_signal加1了,那么就会继续向下执行,并对信号量进行减1。dispatch_time_t timeout可看前面的 dispatch_time_t讲解。
3.dispatch_semaphore_signal的声明为:
这个函数会使传入的信号量的值加1
举个栗子:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
__block long x = 0;
NSLog(@"0_x:%ld",x);
dispatch_async(queue, ^{
x = dispatch_semaphore_signal(semaphore);
NSLog(@"1_x:%ld",x);
x = dispatch_semaphore_signal(semaphore);
NSLog(@"2_x:%ld",x);
dispatch_semaphore_signal(semaphore);
});
x = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"3_x:%ld",x);
x = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"4_x:%ld",x);
x = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"5_x:%ld",x);
输出结果:
2018-07-25 14:37:58.915558+0800 多线程_GCD-18-7-23-0[4464:207019] 0_x:0
2018-07-25 14:37:58.915756+0800 多线程_GCD-18-7-23-0[4464:207019] 3_x:0
2018-07-25 14:37:58.915782+0800 多线程_GCD-18-7-23-0[4464:207068] 1_x:0
2018-07-25 14:37:58.915835+0800 多线程_GCD-18-7-23-0[4464:207019] 4_x:0
2018-07-25 14:37:58.915884+0800 多线程_GCD-18-7-23-0[4464:207068] 2_x:0
2018-07-25 14:37:58.915926+0800 多线程_GCD-18-7-23-0[4464:207019] 5_x:0
⚠️dispatch_semaphore_signal与dispatch_semaphore_wait要成对出现,不然会抛出异常
关于信号量可以这样来比喻:一个停车场拥有五个车位(dispatch_semaphore_create(4)),这时候来了八辆车,每一辆汽车驶进停车位,车位就少了一个(dispatch_semaphore_wait减1),当没有车位的时候(dsema信号量的值为0),其他车子就不能驶进停车位;只有当前面的车从停车位出来(dispatch_semaphore_signal加1),后面的车子才可以进去。
dispatch_once
使用dispatch_once函数可以保证在应用程序执行中只执行一次指定处理的API,可以确保该源代码即使在多线程环境下,也可确保百分之百安全 。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ });
Dispatch I/O 文件读取
在读取较大的文件时,如果将文件分成合适的大小并使用 Global Dispatch Queue 并列读取的话,应该会比一般的读取速度快不少。 在 GCD 当中能实现这一功能的就是 Dispatch I/O 和 Dispatch Data。
异步串行读取文件
dispatch_queue_t queue = dispatch_queue_create("com.myThread.serial", DISPATCH_QUEUE_SERIAL);
NSString *desktop = @"/Users/zq/Desktop/多线程_GCD-18-7-23-0/多线程_GCD-18-7-23-0/“;
NSString *path = [desktop stringByAppendingPathComponent:@"ViewController.m"];
/** 文件描述符 */
dispatch_fd_t fd = open(path.UTF8String, O_RDONLY, 0);
/** 创建一个调度I / O通道,并将其与指定的文件描述符关联 */
dispatch_io_t io_t = dispatch_io_create(DISPATCH_IO_RANDOM, fd, queue, ^(int error) {
close(fd);
});
size_t water = 1024*1024;
/** 设置一次读取的最小字节大小 */
dispatch_io_set_low_water(io_t, water);
/** 设置一次读取的最大字节 */
dispatch_io_set_high_water(io_t, water);
long long fileSize = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil].fileSize;
NSMutableData *totalData = [[NSMutableData alloc] init];
/** 进行文件读取 */
dispatch_io_read(io_t, 0, fileSize, queue, ^(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);
}
});
异步并行读取文件
NSString *desktop = @"/Users/zq/Desktop/多线程_GCD-18-7-23-0/多线程_GCD-18-7-23-0/";
NSString *path = [desktop stringByAppendingPathComponent:@"ViewController.m"];
dispatch_queue_t queue = dispatch_queue_create("com.myThread.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_fd_t fd = open(path.UTF8String, O_RDONLY);
dispatch_io_t io = dispatch_io_create(DISPATCH_IO_RANDOM, fd, queue, ^(int error) {
close(fd);
});
off_t currentSize = 0;
long long fileSize = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil].fileSize;
size_t offset = 1024*1024;
dispatch_group_t group = dispatch_group_create();
NSMutableData *totalData = [[NSMutableData alloc] initWithLength:fileSize];
for (; currentSize <= fileSize; currentSize += offset) {
dispatch_group_enter(group);
dispatch_io_read(io, currentSize, offset, queue, ^(bool done, dispatch_data_t _Nullable data, int error) {
if (error == 0) {
size_t len = dispatch_data_get_size(data);
if (len > 0) {
const void *bytes = NULL;
(void)dispatch_data_create_map(data, (const void **)&bytes, &len);
[totalData replaceBytesInRange:NSMakeRange(currentSize, len) withBytes:bytes length:len];
}
}
if (done) {
dispatch_group_leave(group);
}
});
}
dispatch_group_notify(group, queue, ^{
NSString *str = [[NSString alloc] initWithData:totalData encoding:NSUTF8StringEncoding];
NSLog(@"%@", str);
});
Dispatch Queue实现原理
通常,应用程序中编写的线程管理用的代码要在系统级实现,也就是需要在iOS和OS X的核心XNU内核级上实现,所以无论编程人员如何努力编写管理线程的代码,在性能方面也不可能胜过XNU内核级所实现的GCD。
我们所使用GCD的API全部包含在libdispatch库中的C语言函数。Dispatch Queue通过结构体和链表,被实现为FIFO队列。FIFO队列管理是通过dispatch_async等函数所追加的Block。
屏幕快照 2018-07-25 下午5.27.13.png
Block并不是直接加入FIFO队列,而是先加入Dispatch Continuation这一dispatch_continution_t类型结构体中,然后再加入FIFO队列。
Dispatch Continuation可通过dispatch_set_target_queue函数设定,可以执行该Dispatch Continuation处理的Dispatch Continuation为目标。该目标可以像串珠子一样,设定多个连接在一起的Dispatch Continuation,但是连接串最后必须设定在Main Dispatch Continuation,或各种优先级的Global Dispatch Continuation或是准备与Serial Dispatch Continuation的各种优先级的Global Dispatch Continuation。
在Global Dispatch Continuation中执行Block时,libdispatch从Global Dispatch Continuation自身的FIFO队列中取出Dispatch Continuation,调用pthread_workqueue_addiem_np函数。将该Global Dispatch Continuation自身、符合优先级的workqueue信息以及执行Dispatch Continuation的回掉函数等传递给参数。
pthread_workqueue_addiem_np函数使用works_kernrerurn系统调用,通知workqueue增加应当执行的项目。根据该通知,XNU内核基于系统判断是否要生成线程。如果是OverCommit优先执行的Global Dispatch Continuation,workqueue则始终生成线程。另外,因为workqueue的线程计划表运行,与一般的上下文切换不同。workqueue的线程执行pthread_workqueue函数,该函数调用libdispatch的回掉函数。在该回掉函数中执行加入到Dispatch Continuation的Block。Block执行结束后,进行通知Dispatch Continuation结束、释放Dispatch Continuation等,然后准备执行加入到Global Dispatch Queue中的下一个Block。