美文网首页
GCD详解二

GCD详解二

作者: 蜗牛非牛 | 来源:发表于2018-07-27 15:24 被阅读177次

    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。

    屏幕快照 2018-07-25 下午5.40.07.png

    在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。

    下一篇:正则表达式

    相关文章

      网友评论

          本文标题:GCD详解二

          本文链接:https://www.haomeiwen.com/subject/mmywmftx.html