GCD详解二

2018-07-27  本文已影响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。

下一篇:正则表达式

上一篇下一篇

猜你喜欢

热点阅读