zoukankan      html  css  js  c++  java
  • iOS 多线程 GCD part3:API

    https://www.jianshu.com/p/072111f5889d

    2017.03.05 22:54* 字数 1667 阅读 88评论 0喜欢 1

    0. 预备知识

    GCD对时间的描述有些新奇

    #define NSEC_PER_SEC 1000000000ull

    #define NSEC_PER_MSEC 1000000ull

    #define USEC_PER_SEC 1000000ull

    #define NSEC_PER_USEC 1000ull

    MSEC:毫秒

    USEC:微秒

    NSEC:纳秒

    SEC:秒

    NSEC_PER_SEC,每秒有多少纳秒

    USEC_PER_SEC,每秒有多少毫秒

    #define NSEC_PER_SEC 1000000000ull //GCD最常用的时间描述

    打现在开始往后5秒

    double delayInSeconds = 5.0;

    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC));1->开始时间,2->延后时间

    1. dispatch_group

    1.1 dispatch_group_notify

    当多个任务在并行队列内无序的进行,需要在多个任务全部完成后立刻开启新任务,那么这时就是需要用到

    dispatch_group_async与dispatch_group_notify的组合

        dispatch_queue_t coucurrent_queue = dispatch_queue_create("com.pogong.www", DISPATCH_QUEUE_CONCURRENT);

        

        dispatch_group_t group = dispatch_group_create();

        

        dispatch_group_async(group, coucurrent_queue, ^{

            for (int i = 0; i<10; i++) {

                NSLog(@"group-01 - %@", [NSThread currentThread]);

            }

        });

        

        dispatch_group_async(group, coucurrent_queue, ^{

            for (int i = 0; i<10; i++) {

                NSLog(@"group-02 - %@", [NSThread currentThread]);

            }

        });

        

        dispatch_group_notify(group, coucurrent_queue,^{

            NSLog(@"op finish,start new op");

        });

    1.2 dispatch_group_wait

    还是1.1同样的需求:当任务在并行队列内无序的进行,需要在多个任务结束后立刻开启新任务

    也可以用dispatch_group_async与dispatch_group_wait的组合来完成

        dispatch_queue_t coucurrent_queue = dispatch_queue_create("com.pogong.www", DISPATCH_QUEUE_CONCURRENT);

        

        dispatch_group_t group = dispatch_group_create();

        

        dispatch_group_async(group, coucurrent_queue, ^{

            for (int i = 0; i<10; i++) {

                NSLog(@"group-01 - %@", [NSThread currentThread]);

            }

        });

        

        dispatch_group_async(group, coucurrent_queue, ^{

            for (int i = 0; i<10; i++) {

                NSLog(@"group-02 - %@", [NSThread currentThread]);

            }

        });

        

        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

        NSLog(@"op finish");

    只是需要注意的是dispatch_group_wait会阻塞当前线程,或者说GCD的所有带_wait都会阻塞当前线程

    dispatch_group_wait的第二参数为超时时间,DISPATCH_TIME_FOREVER代表一直等下去.

    也可以将以上dispatch_group_wait代码换成:

    int delayInSeconds = 20;

    dispatch_time_t cheak_Time = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC));

    long res = dispatch_group_wait(group, cheak_Time);

    if (res == 0) {

    NSLog(@"zc done");

    }else{

    NSLog(@"zc ing");

    }

    设置超时时间为20s,

    1.group任务提前完成,提前返回

    2.按指定时间返回

    dispatch_group_wait的返回值为0代表group的任务全部完成,否则就代表任务还在进行中

    1.3 dispatch_async+dispatch_group_enter+dispatch_group_leave

        dispatch_group_t group = dispatch_group_create();

        NSMutableArray * array = [NSMutableArray array];

        for (int i=0; i<5000; i++) {

            dispatch_group_enter(group);//enter和leave成对出现

            dispatch_async(dispatch_get_global_queue(0, 0), ^{

                

                NSLog(@"add in %@", [NSThread currentThread]);

                

                [array addObject:[NSNumber numberWithInt:i]];

                dispatch_group_leave(group);//enter和leave成对出现

                

            });

        }

        NSLog(@"before wait %@", [NSThread currentThread]);

        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    还是1.1同样的需求:当任务在并行队列内无序的进行,需要在多个任务结束后立刻开启新任务

    也可以用dispatch_async+dispatch_group_enter+dispatch_group_leaver的组合来完成

    2.dispatch_after

    double delayInSeconds = 5.0;

    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC));

    dispatch_after(popTime, dispatch_get_main_queue(), ^{

        NSLog(@"zc after");

    });

    dispatch_after的用法如上方代码,就不多说了

    3.信号量dispatch_semaphore

    首先介绍一下信号量(semaphore)的概念。信号量是持有计数的信号,不过这么解释等于没解释。我们举个生活中的例子来看看。

    假设有一个房子,它对应进程的概念,房子里的人就对应着线程。一个进程可以包括多个线程。这个房子(进程)有很多资源,比如花园、客厅等,是所有人(线程)共享的。

    但是有些地方,比如卧室,最多只有两个人能进去睡觉。怎么办呢,在卧室门口挂上两把钥匙。进去的人(线程)拿着钥匙进去,没有钥匙就不能进去,出来的时候把钥匙放回门口。

    这时候,门口的钥匙数量就称为信号量(Semaphore)。很明显,信号量为0时需要等待,信号量不为零时,减去1而且不等待。

    此段描述完全摘自:bestswifer iOS多线程编程——GCD与NSOperation总结

    3.1 多线程资源单一占用

        dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

        dispatch_semaphore_t sem = dispatch_semaphore_create(1);

        

        NSMutableArray * muArr = [NSMutableArray array];

        

        for (int i = 0; i< 1000; i++) {

            dispatch_async(q, ^{

                

                dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

                

                [muArr addObject:@(i)];

                NSLog(@"%d",i);

                

                dispatch_semaphore_signal(sem);

                

            });

        }

    dispatch_semaphore_create,创建一个信号量为1的信号

    dispatch_semaphore_wait,只有信号量大于0才能通过dispatch_semaphore_wait,通过的同时信号量减1

    dispatch_semaphore_signal,信号量加1

    3.2 超时显示

    - (void)downloadFile

    {

        _semaphore = dispatch_semaphore_create(0);

        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://baobab.wdjcdn.com/14525705791193.mp4"] cachePolicy:1 timeoutInterval:30];

        [[self.session downloadTaskWithRequest:request] resume];

        

        double delayInSeconds = 5.0;

        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC));

        

        NSLog(@"zc wait");

        long res = dispatch_semaphore_wait(_semaphore, popTime);

        if (res) {

            NSLog(@"zc timed out");

        }else{

            NSLog(@"zc timed in");

        }

    }

    - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location

    {

        dispatch_semaphore_signal(_semaphore);

        NSLog(@"zc done");

    }

    还是那句所有GCD带_wait函数都会阻塞当前线程

    开始下载创建信号,

    下载成功,增加信号量,

    任务提前完成,提前返回

    按指定时间返回

    dispatch_semaphore_wait的返回值为0代表任务完成,否则就代表任务还在进行中

    3.3 dispatch_semaphore_wait超时时间的理解

    overTime不是调用dispatch_semaphore_wait后等待的时间,而是信号量创建后的时间

    -(void)overTimeTest{

        dispatch_semaphore_t signal = dispatch_semaphore_create(0);

        double delayInSeconds = 5.0;

        dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC));

        

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

            NSLog(@"1 start wait");

            dispatch_semaphore_wait(signal, overTime);

            NSLog(@"需要线程同步的操作1 开始");

            dispatch_semaphore_signal(signal);

        });

        

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

            sleep(3);

            NSLog(@"2 start wait");

            dispatch_semaphore_wait(signal, overTime);

            NSLog(@"需要线程同步的操作2");

            dispatch_semaphore_signal(signal);

        });

    }

    以上两个block内的dispatch_semaphore_wait调用相差3秒,但执行dispatch_semaphore_wait却是同时的.

    4.dispatch_barrier_async

    多个线程对内存中的数组或是字典进行读的操作是OK,

    但如果多个线程对内存中的数组或是字典进行读+写的操作,就会有问题

        NSMutableArray * muArr = [NSMutableArray array];

        [muArr addObject:@"1"];

        [muArr addObject:@"2"];

        [muArr addObject:@"3"];

        dispatch_queue_t con_queue = dispatch_queue_create("com.pogong.www", DISPATCH_QUEUE_CONCURRENT);

        

        dispatch_async(con_queue, ^{

            NSLog(@"%@",muArr);

        });

        

        dispatch_async(con_queue, ^{

            NSLog(@"%@",muArr);

        });

        

        dispatch_barrier_async(con_queue, ^{

            NSLog(@"add add add");

            [muArr addObject:@"4"];

        });

        

        dispatch_async(con_queue, ^{

            NSLog(@"%@",muArr);

        });

        

        dispatch_async(con_queue, ^{

            NSLog(@"%@",muArr);

        });

    dispatch_barrier_async很好解决了多线程读写安全进行的问题,

    在原有的多线程执行时,加入一个dispatch_barrier_async,

    会像有栅栏一样挡住多线程执行,而先执行dispatch_barrier_async,

    在执行完dispatch_barrier_async之后,再继续多线程的操作

    5. dispatch_source

    Dispatch Source用于监听系统的底层对象,比如文件描述符,Mach端口,信号量等。主要处理的事件如下表

    DISPATCH_SOURCE_TYPE_DATA_ADD   数据增加

    DISPATCH_SOURCE_TYPE_DATA_OR    数据OR

    DISPATCH_SOURCE_TYPE_MACH_SEND  Mach端口发送

    DISPATCH_SOURCE_TYPE_MACH_RECV  Mach端口接收

    DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 内存情况

    DISPATCH_SOURCE_TYPE_PROC   进程事件

    DISPATCH_SOURCE_TYPE_READ   读数据

    DISPATCH_SOURCE_TYPE_SIGNAL 信号

    DISPATCH_SOURCE_TYPE_TIMER  定时器

    DISPATCH_SOURCE_TYPE_VNODE  文件系统变化

    DISPATCH_SOURCE_TYPE_WRITE  文件写入

    方法

    dispatch_source_create:创建dispatch source,创建后会处于挂起状态进行事件接收,需要设置事件处理handler进行事件处理。

    dispatch_source_set_event_handler:设置事件处理handler

    dispatch_source_set_cancel_handler:事件取消handler,就是在dispatch source释放前做些清理的事。

    dispatch_source_cancel:关闭dispatch source,这样后续触发的事件时不去调用对应的事件处理handler,但已经在执行的handler不会被取消.

    5.1 dispatch_source_set_timer

    UITableView在被拖拽时NSTimer就不起作用了

    必须加[[NSRunLoop currentRunLoop] addTimer:anyTimer forMode:NSRunLoopCommonModes];

    而dispatch source timer与runloop是没有关系的,所以可以放心使用

    dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0, 0, DISPATCH_TARGET_QUEUE_DEFAULT);

     dispatch_source_set_event_handler(source, ^(){

      NSLog(@"sourceTimer Time log");

     });

     dispatch_source_set_timer(source, DISPATCH_TIME_NOW,5*NSEC_PER_SEC,1*NSEC_PER_SEC);//1->源,2->开始时间,3->间隔时间,4->误差秒数

     _source = source;

     dispatch_resume(_source);

    5.2 文件监听

    监视文件夹内文件变化

    NSURL * directoryURL = [NSURL URLWithString:_path]; // assume this is set to a directory

     int const fd = open([[directoryURL path] fileSystemRepresentation], O_EVTONLY);

     if (fd < 0) {

      char buffer[80];

      strerror_r(errno, buffer, sizeof(buffer));

      NSLog(@"Unable to open "%@": %s (%d)", [directoryURL path], buffer, errno);

      return;

     }

     dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd,

                   DISPATCH_VNODE_WRITE | DISPATCH_VNODE_DELETE, DISPATCH_TARGET_QUEUE_DEFAULT);

     dispatch_source_set_event_handler(source, ^(){

      unsigned long const data = dispatch_source_get_data(source);

      if (data & DISPATCH_VNODE_WRITE) {

       NSLog(@"The directory changed.");

      }

      if (data & DISPATCH_VNODE_DELETE) {

       NSLog(@"The directory has been deleted.");

      }

     });

     dispatch_source_set_cancel_handler(source, ^(){

      close(fd);

     });

     _source = source;

     dispatch_resume(_source);

    还要注意需要用DISPATCH_VNODE_DELETE去检查监视的文件或文件夹是否被删除,如果删除了就停止监听

    6. dispathc_once

    dispathc_once函数可以确保某个 block 在应用程序执行的过程中只被处理一次,而且它是线程安全的。所以单例模式可以很简单的实现,写单例必备

    + (Manager *)sharedInstance {

        static Manager *sharedManagerInstance = nil;

        static dispatch_once_t once;

        dispatch_once($once, ^{

            sharedManagerInstance = [[Manager alloc] init];

        });

        return sharedManagerInstance;

    }

    7. dispathc_apply

    7.1 并行队列完成任务,开启新任务

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_apply(10, queue, ^(size_t index) {

        NSLog(@"%d ",index);

    });

    NSLog(@"done");

    打印结果为:

    2017-03-04 21:40:43.454 GCDTrain[1289:42894] 0

    2017-03-04 21:40:43.454 GCDTrain[1289:42937] 3

    2017-03-04 21:40:43.454 GCDTrain[1289:42940] 2

    2017-03-04 21:40:43.454 GCDTrain[1289:42894] 4

    2017-03-04 21:40:43.454 GCDTrain[1289:42894] 5

    2017-03-04 21:40:43.454 GCDTrain[1289:42938] 1

    2017-03-04 21:40:43.455 GCDTrain[1289:42894] 8

    2017-03-04 21:40:43.454 GCDTrain[1289:42940] 6

    2017-03-04 21:40:43.455 GCDTrain[1289:42937] 7

    2017-03-04 21:40:43.455 GCDTrain[1289:42938] 9

    2017-03-04 21:40:43.455 GCDTrain[1289:42894] done

    所以这又是一种对多线程无序的限定的API,dispatch_applyblock内的所有任务被执行完之后才会执行后面的代码,当然dispatch_apply与前面提到的所有带_wait的API一样都是阻塞当前线程的

    dispatch_apply与带_wait的API也有不同,dispatch_apply比较适合做些重复的+执行次数确定的任务

    7.2 防止开启线程过多

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    for (int i = 0; i < 999; i++){

          dispatch_async(queue, ^{

             NSLog(@"%d,%@",i,[NSThread currentThread]);

          });

    }

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_apply(999, queue, ^(size_t i){

         NSLog(@"%d,%@",i,[NSThread currentThread]);

    });

    看两份代码的打印结果可知道,不用dispatch_apply的并行+异步会开启许多线程,而我们已经知道:「使用太多线程会导致消耗大量内存」,所以在这种场景下应该使用dispatch_apply

    8.dispatch_set_target_queue

    8.1dispatch_set_target_queue可以设置queue的优先级

    dispatch_queue_create创建队列的优先级跟global dispatch queue的默认优先级一样,假如我们需要设置队列的优先级,可以通过dispatch_set_target_queue方法

    dispatch_queue_t serialQueue = dispatch_queue_create("com.pogong.www", DISPATCH_QUEUE_SERIAL);  

    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);  

    dispatch_set_target_queue(serialQueue, globalQueue);

    //这样serialQueue的优先级和globalQueue的优先级一样

    8.2 定义队列层级关系

    将多个队列用dispatch_set_target_queue设置为某个串行队列的下属队列,可以防止并行执行

    • 加队列层级关系

        dispatch_queue_t targetQueue = dispatch_queue_create("target_queue", DISPATCH_QUEUE_SERIAL);

        dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);

        dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);

        dispatch_set_target_queue(queue1, targetQueue);//加层级关系

        dispatch_set_target_queue(queue2, targetQueue);//加层级关系

        dispatch_async(queue1, ^{

            NSLog(@"do job1");

        });

        dispatch_async(queue2, ^{

            NSLog(@"do job2");

        });

        dispatch_async(queue2, ^{

            NSLog(@"do job3");

        });

        dispatch_async(queue2, ^{

            NSLog(@"do job4");

        });

        dispatch_async(queue2, ^{

            NSLog(@"do job5");

        });

        dispatch_async(queue2, ^{

            NSLog(@"do job6");

        });

    2017-03-04 22:07:40.723 GCDTrain[1400:51089] do job1

    2017-03-04 22:07:40.723 GCDTrain[1400:51089] do job2

    2017-03-04 22:07:40.724 GCDTrain[1400:51089] do job3

    2017-03-04 22:07:40.724 GCDTrain[1400:51089] do job4

    2017-03-04 22:07:40.724 GCDTrain[1400:51089] do job5

    2017-03-04 22:07:40.725 GCDTrain[1400:51089] do job6

    • 不加队列层级关系

        dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);

        dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);

        dispatch_async(queue1, ^{

            NSLog(@"do job1");

        });

        dispatch_async(queue2, ^{

            NSLog(@"do job2");

        });

        dispatch_async(queue2, ^{

            NSLog(@"do job3");

        });

        dispatch_async(queue2, ^{

            NSLog(@"do job4");

        });

        dispatch_async(queue2, ^{

            NSLog(@"do job5");

        });

        dispatch_async(queue2, ^{

            NSLog(@"do job6");

        });

    2017-03-04 22:01:04.781 GCDTrain[1365:48597] do job2

    2017-03-04 22:01:04.781 GCDTrain[1365:48598] do job1

    2017-03-04 22:01:04.781 GCDTrain[1365:48599] do job3

    2017-03-04 22:01:04.781 GCDTrain[1365:48609] do job5

    2017-03-04 22:01:04.781 GCDTrain[1365:48608] do job4

    2017-03-04 22:01:04.782 GCDTrain[1365:48597] do job6

    打印结果一目了然

    9. dispatch IO

    在书上,在别人的文章里对dispatch IO都是一段引用苹果官方的代码,跑不起来,自己真会用了再说

    10. 未完待续

    还有很多API并未见过介绍,所以.....

    文章参考:

    Objective-C高级编程:iOS与OS X多线程和内存管理

    bestswifer iOS多线程编程——GCD与NSOperation总结

    戴铭 细说GCD如何用

    写在最前面:
    转自:https://blog.csdn.net/wpeng20125/article/details/73650569
    对原文作了下排版利于理解,也感谢原文作者为我们说明这个函数

    dispatch_time_t 类型,它的创建有两个函数

      1. dispatch_time(<#dispatch_time_t when#>, <#int64_t delta#>)
        第一个参数是从什么时间开始,一般直接传
        DISPATCH_TIME_NOW表示从现在开始
        第二个参数表示具体的时间长度(不能直接传 int 或 float), 可以写成这种形式 (int64_t)3* NSEC_PER_SEC
            #define NSEC_PER_MSEC 1000000ull    每毫秒有1000000纳秒
            #define USEC_PER_SEC 1000000ull     每秒有1000000微秒
            #define NSEC_PER_USEC 1000ull       每微秒有1000纳秒
    

    注意 delta 的单位是纳秒!
    1秒的写作方式可以是 1* NSEC_PER_SEC; 1000* NSEC_PER_MSEC或 USEC_PER_SEC* NSEC_PER_USEC

    • 2.dispatch_walltime(<#const struct timespec * _Nullable when#>, <#int64_t delta#>),
      第一个参数是一个结构体, 创建的是一个绝对的时间点,比如 2016年10月10日8点30分30秒, 如果你不需要自某一个特定的时刻开始,可以传 NUll,表示自动获取当前时区的当前时间作为开始时刻, 第二参数意义同第一个函数
      dispatch_time_t time = dispatch_walltime(NULL, 5* NSEC_PER_SEC);

    两个函数的不同
    例如:
    从现在开始,1小时之后是触发某个事件

    使用第一个函数创建的是一个相对的时间,第一个参数开始时间参考的是当前系统的时钟,当 device 进入休眠之后,系统的时钟也会进入休眠状态, 第一个函数同样被挂起; 假如 device 在第一个函数开始执行后10分钟进入了休眠状态,那么这个函数同时也会停止执行,当你再次唤醒 device 之后,该函数同时被唤醒,但是事件的触发就变成了从唤醒 device 的时刻开始,1小时之后.

    而第二个函数则不同,他创建的是一个绝对的时间点,一旦创建就表示从这个时间点开始,1小时之后触发事件,假如 device 休眠了10分钟,当再次唤醒 device 的时候,计算时间间隔的时间起点还是,开始时就设置的那个时间点, 而不会受到 device 是否进入休眠影响



    作者:zcc_ios
    链接:https://www.jianshu.com/p/9e2c363a09e2
    來源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
  • 相关阅读:
    Alpha发布——美工+文案展示博客
    021_func
    020_with
    018_IO
    017_set
    016_dict
    015_string
    012_list
    010_ternaryOperation
    008_standard_lib_os
  • 原文地址:https://www.cnblogs.com/sundaysme/p/10386633.html
Copyright © 2011-2022 走看看