GCD详细介绍

来源:互联网 发布:路径动画导入unity3d 编辑:程序博客网 时间:2024/04/28 12:23

GCD

GCD是异步执行任务的技术。只需将想执行的任务追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。

1 Dispatch Queue:执行处理的等待队列

Disptatch Queue按照追加顺序执行处理任务,但在执行处理又分为:Serial Dispatch Queue和Concurrent Dispatch Queue。
Serial Dispatch Queue:串行队列,等待现在执行中的处理结束
Concurrent Dispatch Queue:并行队列,不等待现在执行中的处理结束

在Concurrent Dispatch Queue中,并行执行的处理数量取决于当前系统的状态,即基于Dispatch Queue中的任务数、CPU核数、以及CPU负荷等。所谓并行,就是使用多个线程同时执行多个处理。

注意事项:(两种队列对比)
1.Concurrent Dispatch Queue并行执行多个追加处理,而Serial Dispatch Queue同时只能执行1个追加处理。
2.当生成多个Serial Dispatch Queue时,各个Serial Dispatch Queue将并行执行。如果过多使用多线程,就会消耗大量内存,引起大量的上下文切换,大幅度降低系统的响应性能。
3.使用Concurrent Dispatch Queue,XNU内核只使用有效管理的线程,不会发生Serial Dispatch Queue的那些问题。
4.只在为了避免多线程编程问题之一—多个线程更新相同资源导致数据竞争时使用Serial Dispatch Queue。

2生成Dispatch Queue

2.1通过dispatch_queue_create

dispatch_queue_tmyConcurrentDispathQueue =dispatch_queue_create("com.example.gcd.MyConcurrentDispathQueue",DISPATCH_QUEUE_CONCURRENT);
第一个参数为Dispatch Queue的名称,推荐使用逆序域名。该名称在Xcode和Instruments的调试器中作为Dispatch Queue名称表示,也出现在应用程序崩溃时所生成的CrashLog中。
第二个参数:指定为NULL生成Serial Dispatch Queue;指定为DISPATCH_QUEUE_CONCURRENT,生成Concurrent Dispatch Queue。
返回值为表示Dispatch Queue的“dispatch_queue_t类型”。

对于最低sdk版本>=ios6.0,GCD对象已经纳入了ARC的管理范围。如果<6.0,也像Objective的引用计数内存管理一样,需要通过dispatch_retain和dispatch_release函数的引用计数来管理内存。
1)通过dispatch_queue_create生成的Dispatch Queue在使用结束后通过dispatch_release函数释放:
dispatch_release(myConcurrentDispathQueue);
2)把Block加到Dispatch Queue后,Block会保留Dispatch Queue。

2.2获取系统标准提供的Dispatch Queue:Main Dispatch Queue和Global Dispatch Queue

Main Dispatch Queue:是在主线程中执行的队列。因为主线程只有一个,所以Main Dispatch Queue自然就是Serial Dispatch Queue。将用户界面更新等一些必须在主线程中执行的处理追加到Main Dispatch Queue。

Global Dispatch Queue:所有应用程序都能够使用的Concurrent Dispatch Queue。没有必要通过dispatch_queue_create函数逐个生成Concurrent Dispatch Queue。只要获取Global Dispatch Queue。
Global Dispatch Queue有4个执行执行优先级:高优先级、默认优先级、低优先级、后台优先级。

各种Dispatch Queue的获取方法:
/*
     *Main Dispatch Queue
的获取方法
     */

   
dispatch_queue_t mainDispatchQueue =dispatch_get_main_queue();
   
   
/*
     *Global Dispatch Queue
(高优先级)的获取方法
     */
    dispatch_queue_t globalDispatchQueue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);

3 dispatch_set_target_queue?

dispatch_queue_create函数生成的Dispatch Queue都使用与默认优先级Global Dispatch Queue相同执行优先级的线程。而变更生成的Dispatch Queue 的执行优先级要使用dispatch_set_target_queue函数。

4 dispatch_after

在3秒后将指定的Block追加到Main Dispatch Queue中:
dispatch_time_ttime = dispatch_time(DISPATCH_TIME_NOW,3ull *NSEC_PER_SEC);
    dispatch_after_f(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有大量处理追加或主线程的处理本身有延时,这个时间更长。

5 Dispatch Group

Concurrent Dispatch Queue或同时使用多个Dispatch Queue的多个处理全部结束后想执行结束处理,在这种情况下使用Dispatch Group。
    dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_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_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_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);

或者指定等待时间再往下处理(指定1秒):
    dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_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_time_t time =dispatch_time(DISPATCH_TIME_NOW,1ull *NSEC_PER_SEC);
   
long result = dispatch_group_wait(group, time);
   
if (result == 0) {
       
//属于Dispatch Group的全部处理执行结束
    }
这里的“等待”意味着:执行dispatch_group_wait函数的线程(当前线程)停止。在经过dispatch_group_wait函数中指定的时间或属于指定Dispatch Group的处理全部执行结束之前,执行该函数的线程停止。

6 dispatch_barrier_async

写入处理文件不可与其他写入处理以及包含读取处理的其他某些处理并行执行。但读取处理可以和读取处理并行执行。
为了高效率地进行访问,读取处理追加到Concurrent Dispatch Queue中,写入处理在任一个读取处理没有执行的状态下,追加到Serial Dispatch Queue中。利用Dispatch Group和 dispatch_set_target_queue函数也可实现
1.将写之前的所有读处理加入Concurrent Dispatch Queue,并绑定到一个Dispatch Group中,这个group的所有读操作处理完了之后,再把写操作加入Serial Dispatch Queue中。
2.把读操作放进Concurrent Dispatch Queue中,写操作放进Serial Dispatch Queue,用dispatch_set_target_queue设置这两个队列的优先级。

使用dispatch_barrier_async函数。dispatch_barrier_async函数会等待追加到Concurrent Dispatch Queue上的并行处理全部结束之后,再将指定的处理追加到该Concurrent Dispatch Queue中。然后在由dispatch_barrier_async函数追加的处理执行完毕后,Concurrent Dispatch Queue才恢复为并行执行。
    dispatch_async(queue, blk0_for_reading);
    dispatch_async(queue, blk1_for_reading);
    dispatch_async(queue, blk2_for_reading);
    dispatch_async(queue, blk3_for_reading);
    dispatch_barrier_async(queue, blk_for_writing);
    dispatch_async(queue, blk4_for_reading);
    dispatch_async(queue, blk5_for_reading);
如图,

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

7 dispatch_sync

dispatch_async函数将指定的Block“异步”地追加到指定的Dispatch Queue中,不做任何等待就往下执行,如图

dispatch_sync函数将指定的Block“同步”地追加到指定的Dispatch Queue中,等到该Block执行完后再往下执行,如图。

使用dispatch_sync函数很容易导致死锁,尤其在Serial Dispatch Queue中以dispatch_sync方式追加Block就会导致死锁。因为因为Serial Dispatch Queue只有一个线程。
以下三个代码都会产生死锁。
代码1:
dispatch_queue_tqueue = dispatch_get_main_queue();
dispatch_sync(queue, ^{NSLog(@"Hello?");});
主线程在等待Block执行完再往下执行,所以主线程无法执行block,block无法执行,主线程就不能往下执行,导致死锁。

代码2:
    dispatch_queue_t queue =dispatch_get_main_queue();
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
                    NSLog(@"Hello?");
        });
    });
因为main_queue只有一个主线程,两个block都是使用main_queue,跟上面的代码一样会导致死锁。

代码3.
dispatch_queue_tqueue =  dispatch_queue_create("com.example.gcd.MySerialDispathQueue",NULL);
   
dispatch_async(queue, ^{
       
dispatch_async(queue, ^{
                   
NSLog(@"Hello?");
        });
    });

如果queue为Concurrent Dispatch Queue则不会出现死锁,在queue中可以分配多个线程;
如果两个block加入的是不同的queue也不会出现死锁,因为不同的queue使用不同的线程。

8 dispatch_apply

dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API。该函数按指定的次数将指定的block追加到指定的Dispatch Queue中,并等待全部处理执行结束。
dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
   
dispatch_apply(10, queue, ^(size_tindex) {
       
NSLog(@"%zu", index);
    });
    NSLog(@"done");
index是各个block的序号,区分各个block。

由于dispatch_apply函数也与dispatch_sync函数相同,会等待处理执行结束,因此推荐在dispatch_async函数中非同步地执行dispatch_apply函数。
    dispatch_queue_t queue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
   
/*
     *
Global Dispatch Queue中非同步执行
     */

   
dispatch_async(queue, ^ {
       
       
/*
         *Global Dispatch Queue
         *
等待dispatch_apply函数全部处理执行结束
         */

       
dispatch_apply([arraycount], queue, ^(size_tindex) {
           
NSLog(@"%zu: %@", index, [arrayobjectAtIndex:index]);
        });
       
       
/*
         *dispatch_apply
函数中处理全部执行结束
         */

       
       
/*
         *
Main Dispatch Queue中非同步执行
         */

       
       
dispatch_async(dispatch_get_main_queue(), ^{
           
           
/*
             *
Main Dispatch Queue中执行处理
             *
用户界面更新等
             */

           
NSLog(@"done");
        });

    });

9 Dispatch Semaphore

并行执行更新数据时,会产生数据不一致的情况。如:
dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
   
NSMutableArray *array = [[NSMutableArrayalloc]init];
   
for (inti=0; i<10000; i++) {
       
dispatch_async(queue, ^{
            [array
addObject:[NSNumbernumberWithInt:i]];
        });
    }
有可能同时有两个线程添加数据,从而导致一个数据没被添加。

Serial Dispatch Queue和dispatch_barrier_async函数可以避免数据同步的问题(每次只有一个任务在执行),但Dispatch Semaphore是更细粒度的对象。
Dispatch Semaphore是持有计数的信号量。通过dispatch_semaphore_create函数生成Dispatch Semaphore
dispatch_semaphore_tsempahore = dispatch_semaphore_create(1);

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

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
     *Dispatch Semaphore
的技术初始值设定为1.
     *
保证可访问NSMutableArray类对象的线程,同时只能有1
     */

   
dispatch_semaphore_t sempahore =dispatch_semaphore_create(1);
   
NSMutableArray *array = [[NSMutableArrayalloc]init];
   
for (inti=0; i<10000; i++) {
       
dispatch_async(queue, ^{
           
/*
             *
等待Dispatch Semaphore
             *
一直等待,直到Dispatch Semaphore的计数值达到大于等于1.
             */

           
dispatch_semaphore_wait(sempahore,DISPATCH_TIME_FOREVER);
           
/*
             *
由于Dispatch Semaphore的计数值达到大于等于1
             *
所以将Dispatch Semaphore的计数值减去1
             *dispatch_sempahore_wait
函数执行返回
             *
             *
由于可访问NSMutableArray类对象的线程
             *
只有1
             *
因此可安全地进行更新。
             */

            [array
addObject:[NSNumbernumberWithInt:i]];
           
           
/*
             *
排他控制处理结束,
             *
所以通过dispatch_semaphore_signal函数
             *
Dispatch Semaphore的计数值加1.
             */

           
dispatch_semaphore_signal(sempahore);
        });

10.dispatch_once

使用dispatch_once来执行只需运行一次的线程安全代码
单例模式:
(1)使用同步块:
使用同步块:
+ (id)sharedInstance {
   
static EOCClass *sharedInstance =nil;
   
@synchronized(self) {
       
if (!sharedInstance) {
            sharedInstance = [[
selfalloc]init];
        }
    }
   
return sharedInstance;
}

(2)使用dispatch_once
+ (id)sharedInstance {
   
static EOCClass *sharedInstance =nil;
   
static dispatch_once_t oneToken;
   
dispatch_once(&oneToken, ^{
        sharedInstance = [[
selfalloc]init];
    });
   
return sharedInstance;
}
此函数接受类型为dispatch_once_t的特殊参数,成为“标记”。对于给定的标记来说,该函数保证相关的块必定会执行,且仅执行一次。对于只需执行一次的块来说,每次调用函数时传入的标记都必须完全相同。由于每次调用时都必须使用完全相同的标记,所以标记要声明为static。把该变量定义在static作用域中,可以保证编译器在每次执行sharedInstance方法时都会复用这个变量,而不会创建新变量。
使用dispatch_once可以保证线程安全。 
0 0
原创粉丝点击