ios-多线程编程

一、多线程概念

  基本上1个CPU核一次能够执行的CPU命令始终为1,由于使用多线程的程序可以再某个线程和其他线程之间多次进行上下文切换,因此看上去就好像1个CPU核能够并列地执行多个线程一样,而且在具有多个CPU核的情况下,是真的提供了多个CPU核并行执行多个线程;

  多线程可以保证应用程序的相应性能;

  多线程编程是一种易发生各种问题的编程技术:

  1、多个线程更新相同的资源会导致数据的不一致(数据竞争);

  2、停止等待事件的线程会导致多个相互持续等待(死锁);

  3、使用太多线程会消耗大量的内存;

  长时间的处理不在主线程中执行(会妨碍主线程的执行、阻塞主线程),如AR用画像的识别或数据库访问;

二、GCD的API

  1、Dispatch Queue

    Dispatch Queue 是执行处理的等待队列,应用程序编程人员通过dispatch_async函数等API,将要执行的Block追加到Dispatch Queue中;

    Dispatch Queue 按照追加的顺序(先进先出 FIFO,FIrst-In-First-Out)执行处理;

    Dispatch Queue 的种类:

      1、等待现在执行中处理的 Serial Dispatch Queue,同时只能执行一个追加处理;

      2、不等待现在执行中处理的Concurrent Dispatch Queue,并行执行多个追加处理;

    Concurrent Dispatch Queue 并行执行的数量取决于当前系统的状态,即iOS基于Dispatch Queue中的处理数、CPU核数以及CPU负荷等;

  2、dispatch_queue_create

 

    用 dispatch_queue_create 函数可生成任意多个 Diapatch Queue,当生成多个Serial Dispatch Queue 时,各个Serial Dispatch Queue将并行执行,一旦生成 Serial Dispatch Queue 并追加处理,系统对于一个 Serial Dispatch Queue 就只生成并使用一个线程;

    如果过多使用线程,就会消耗大量内存,引起大量的上下文切换,大幅降低系统的相应性能;

    为了避免多个线程更新相同资源导致数据竞争时,可以使用 Serial Dispatch Queue;

    当想并行执行不发生数据竞争等问题的处理时,使用 Concurrent Dispatch Queue ,对于 Concurrent Dispatch Queue 来说,不管生成多少,由于XNU内核只使用有效管理的线程,因此不会发生 Serial Dispatch Queue 的那些问题;

    通过 dispatch_queue_create 函数可以生产Diapatch Queue;

    dispatch_queue_t mySerialDispatchQueue =   dispatch_queue_create("com.apply.gcd.MySerialDispatchQueue",  NULL);

    第一个参数指定Dispatch queue 的名称(可设置为NULL,建议使用"com.apply.gcd.MySerialDispatchQueue"类似的名称,便于调试);

    第二个参数指定为NULL时,生成 Serial Dispatch Queue ;指定为 DISPATCH_QUEUE_CONCURRENT 时,生成Concurrent Dispatch Queue;

    dispatch_queue_create 函数的返回值为表示 Dispatch Queue 的“dispatch_queue_t”类型;

    dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("com.apply.gcd.MySerialDispatchQueue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(myConcurrentDispatchQueue, ^{

      NSLlog(@"block on myConcurrentDispatchQueue");

    });

    上述源代码在 Concurrent Dispatch Queue 中执行指定的 Block;

    此外在ios6及以后版本,GCD的对象都是ARC自动管理内存;

  3、Main Dispatch Queue/Global Dispatch Queue

    Main Dispatch Queue 是在主线程中执行的 Dispatch Queue,因为主线程只有一个,所以自然也就是 Serial Dispatch Queue;

    追加到 Main Dispatch Queue 的处理在主线程指定 RunLoop 中执行,因此要将用户界面的界面更新等一些必须在主线程中执行的处理追加到 Main Dispatch Queue 使用;

    获取 Main Dispatch Queue 的方法:

    dispatch_queue_t queue = dispatch_get_global_queue();

    Global Dispatch Queue 是所有应用程序都能够使用的 Current Dispatch Queue,没必要通过 dispatch_queue_create 函数逐个生成 Current Dispatch Queue ,只要获取Global Dispatch Queue使用即可;

    获取 Global Dispatch Queue 的方法:

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

    Global Dispatch Queue 有4个执行优先级:

    高优先级:DISPATCH_QUEUE_PRIORITY_HIGH;

    默认优先级:DISPATCH_QUEUE_PRIORITY_DEFAULT;

    低优先级:DISPATCH_QUEUE_PRIORITY_LOW;

    后台优先级:DISPATCH_QUEUE_PRIORITY_BACKGROUND;

  4、dispatch_set_target_queue

    dispatch_queue_create 函数生成的 Dispatch Queue 不管是 Serial Dispatch 还是 Concurrent Dispatch Queue ,都使用与默认优先级 Global Dispatch Queue 相同执行优先级的线程;

    dispatch_set_target_queue 函数可以变更生成的 Dispatch Queue 的执行优先级;

    dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.apply.gcd.MySerialDispatchQueue", NULL);

    dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

    dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);

    指定要变更执行优先级的 Dispatch Queue 为 dispatch_set_target_queue 函数的第一个参数,指定与要只用的执行优先级相同优先级的 Global Dispatch Queue为第二个参数,如果第一个参数指定系统提供的 Main Dispatch Queue 和 Global Dispatch Queue 则不知道会出现什么状况,因此这些均不可指定;

    如果在多个 Serial Dispatch Queue 中用 dispatch_set_target_queue 函数指定目标为某一个 Serial Dispatch Queue,那么原先本应并行的多个 Serial Dispatch Queue,在目标 Serial Dispatch Queue 上只能同时执行一个处理;在必须将不可并行执行的处理追加到多个 Serial Dispatch Queue 中时,如果使用 dispatch_set_target_queue 函数目标指定为某一个 Serial Dispatch Queue,即可防止处理并行执行;

   5、dispatch_after

    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);

    dispatch_after(time, dispatch_get_main_queue(),^{

      NSLog(@"waited at least three seconds.");

    });

    需要注意的是,dispatch_after 函数并不是在指定时间后执行处理,而只是在指定时间追加处理到 Dispatch Queue ;

    因为 Main Dispatch Queue 在主线程的 RunLoop 中执行,所以在比如每个 1/60 秒执行的 RunLoop 中,Block 最快在3秒后执行,最慢在3秒 + 1/60秒后执行,并且在 Main Dispatch Queue 有大量处理追加或主线程的处理本身有延迟时,这个时间会更长;

    第一个参数是指定时间用的 dispatch_time_t 类型的值,该值使用 dispatch_time 函数或 dispatch_walltime 函数作成;

    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);

    数值和 NSEC_PER_SEC 的乘积得到的单位为毫微秒的数值,"ull" 是C语音的数值字面量,是显式表明类型时使用得到字符串(表示"unsigned long long");如果使用 NSEC_PER_MSEC 则可以以毫秒为单位计算;

    dispatch_time 函数通常用于计算相对时间,而 dispatch_walltime 函数用于计算绝对时间;例如在 dispatch_walltime 函数中想指定 2011 年 11 月 11日 11 时 11 分 11 秒这一绝对时间的情况,这可作为粗略的闹钟功能使用;

    dispatch_time_t getDispatchTimeByDate(NSDate * date){

      NSTimeInterval interval;

      double second, subsecond;

      struct timespec time;

      dispatch_time_t milestone;

      

      interval = [date timeIntervalSince1970];

      subsecond = modf(interval, &second);

      time.tv_sec = second;

      time.tv_nsec = subsecond * NSEC_PER_SEC;

      milestone = dispatch_walltime(&time, 0);

      return milestone;

    }

    上面源代码可由NSDate 类对象获取能传递给 dispatch_after 函数的 dispatch_time_t 类型的值;

  6、Dispatch Group

    在追加到 Dispatch Queue 中的的多个处理全部结束后想执行处理,只是用一个 Serial Dispatch Queue 时,只要将想执行的处理全部追加到该 Serial Dispatch Queue 中并在最后追加结束处理,即可实现;但是在使用 Concurrent Dispatch Queue 时或同时使用多个 Dispatch Queue 时,可以使用 Dispatch Group;

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_PRIORITY_DEFAULT, 0);

    dispatch_group_t group = dispatch_group_create();

    dispatch_group_async(group, queue, ^{NSLog(@"blk0");});

    dispatch_group_async(group, queue, ^{NSLog(@"blk1");});

    dispatch_group_async(group, queue, ^{NSLog(@"blk2");});

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"done");});

 

    在 Dispatch Group 中也可以使用 dispatch_group_wait 函数仅等待全部处理执行结束;

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_PRIORITY_DEFAULT, 0);

    dispatch_group_t group = dispatch_group_create();

    dispatch_group_async(group, queue, ^{NSLog(@"blk0");});

    dispatch_group_async(group, queue, ^{NSLog(@"blk1");});

    dispatch_group_async(group, queue, ^{NSLog(@"blk2");});

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    dispatch_group_wait 函数的第二个参数指定为等待的时间(超时);它属于 dispatch_time_t 类型的值;上述源代码使用 DISPATCH_TIME_FOREVER ,意味着永久等待,只要属于 Dispatch Group 的处理尚未执行结束,就好一直等待,中途不能取消;

    如果指定的超时时间为 1 秒时应做如下处理;、

    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);

    long result = dispatch_group_wait(group, time);

    if(result == 0){

      //属于 Dispatch Group 的处理全部执行结束

    } else {

      //属于 Dispatch Group 的某一个处理还在执行中

    }

    如果 dispatch_group_wait 函数的返回值不为 0 ;就意味着虽然经过了指定的时间,但属于 Dispatch Group 的某一个处理还在执行中,如果返回值为 0 ,那么全部处理执行结束;当等待时间为 DISPATCH_TIME_FOREVER 时,返回值恒为 0 ;

  7、dispatch_barrier_async

    dispatch_queue_t queue = dispatch_get_global_queue("com.apply.gcd.Queue", 0);

    dispatch_async(queue, ^{NSLog(@"done0");});

    dispatch_async(queue, ^{NSLog(@"done1");});

    dispatch_barrier_async(queue, ^{NSLog(@"barrier");});

    dispatch_async(queue, ^{NSLog(@"done2");});

    dispatch_async(queue, ^{NSLog(@"done3");});

    dispatch_barrier_async 函数会等待追加到 Concurrent Dispatch Queue 上的并行执行的处理全部结束之后,再将指定的处理追加到该 Concurrent Dispatch Queue 中,然后再由 dispatch_barrier_async 函数追加的处理执行完毕后,后面追加到该 Concurrent Dispatch Queue 的处理又开始并行执行;

    使用 Concurrent Dispatch Queue 和 Dispatch_barrier_async 函数可实现高效率的数据库访问和文件访问;

  8、dispatch_sync

    dispatch_sync 将指定的 Block “同步”追加到指定的 Dispatch Queue 中,在追加Block 结束之前,dispatch_sync 函数会一直等待,等待意味着当前线程停止;

    dispatch_sync 函数容易造成“死锁”;

    dispatch_queue_t queue = dispatch_get_main_queue();

    dispatch_sync(queue, ^{NSLog(@"Hello?");});

    上述源代码在 Main Dispatch Queue 即主线程中执行指定的 Block ,并等待其执行结束,而其实在主线程中正在执行这些源代码,所以无法执行追加到 Main Dispatch Queue 的 Block;

    dispatch_queue_t queue = dispatch_get_main_queue();

    diapatch_async(queue, ^{

      dispatch_sync(queue, ^{NSLog(@"Hello?");});

    });

    上述源代码也会造成死锁;

  9、dispatch_apply

    dispatch_apply 函数是 dispatch_sync 函数和 Dispatch Group 的关联 API ,该函数指定的次数将指定的 Block 追加到指定的 Dispatch Queue 中,并等待全部执行结束;

    dispatch_queue_t queue = diapatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

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

      NSLog(@"%zu", ndex);

    });

    NSLog(@"done");

    因为在 Global Dispatch Queue 中执行处理,所以各个处理的执行时间不定,但是输出结果中最后的 done 必定在最后的位置上,这是因为 dispatch_apply 函数会等待全部处理执行结束;

  10、dispatch_suspend/dispatch_resume

    dispatch_suspend 函数挂起指定的 Dispatch Queue;

    dispatch_suspend(queue);

    dispatch_resume 函数恢复指定的 Dispatch Queue;

    dispatch_resume(queue);

  11、Dispatch Semaphore

    Dispatch Semaphore 是持有计数的信号,该计数是多线程编程中的计数类型信号,计数为 0 时等待,计数为 1 或大于 1 时,减去 1 而不等待;

    通过 dispatch_semaphore_create 函数生成 Dispatch Semaphore;

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

    参数表示计数的初始值;

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    dispatch_semaphore_wait 函数等待 Dispatch Semaphore 的计数值达到大于或等于 1;当计数大于等于 1;或者在待机中计数值大于等于 1 时;对该计数进行减法并从 dispatch_semaphore_wait 函数返回,第二个参数与 dispatch_group_wait 函数等相同,由 dispatch_time_t 类型值指定等待时间;

    dispatch_time_t time = dispatch_time("DISPATCH_TIME_NOW", 1ull * NSEC_PER_SEC);

    long result = dispatch_semaphore_wait(semaphore, time);

    if ( result == 0){

      // 由于 Dispatch Semaphore 的计数值达到大于等于 1

      // 所以 Dispatch Semaphore 的计数值减去 1

      // 可执行需要进行排他控制的处理

    } else {

      // 由于 Dispatch Semaphore 的计数值为 0

      // 因此在达到指定时间为止待机

    }

    dispatch_semaphore_wait 函数返回 0 时,可安全地执行需要进行排他控制的处理,该处理结束时通过 dispatch_semaphore_signal 函数将 Dispatch Semaphore 的计数值加 1;

    dispatch_queue_t queue = dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_semaphore_t semaphore = dispatch_semaphore_create( 1 );

    NSMutableArray * array = [[NSMutableArray alloc] init];

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

      dispatch_async(queue, ^{

        dispatch_semaphore_wait( semaphore , DISPATCH_TIME_FOREVER);

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

        dispatch_semaphore_signal(semaphore);

      });

    }

  12、Dispatch_once

    dispatch_once 函数是保证在应用程序执行中只执行一次指定处理的 API;

 

posted @ 2017-07-18 17:46  晚安早安  阅读(101)  评论(0编辑  收藏  举报