美文网首页互联网程序员iOS学习笔记
《Objective-C高级编程》多线程编程GCD详解

《Objective-C高级编程》多线程编程GCD详解

作者: _誌念 | 来源:发表于2017-12-13 23:36 被阅读213次
    图片来自网络

    什么是GCD

    什么是GCD?下面是苹果的官方说明。

    Grand Central Dispatch (GCD) 是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。也就是说GCD用我们难以置信的非常简洁的计述方法,实现了极为复杂繁琐的多线程编程。

    多线程编程

    一个CPU一次只能执行一个命令,不能执行某处分开的并列的两个命令,因此通过CPU执行的CPU命令列就好比一条无分叉的大道,其执行不会出现分歧。
    这里所说的 " 一个CPU执行的CPU命令列为一条无分叉的路径" 即为 "线程"。

    现在一个物理的CPU芯片实际上有64个(64核)CPU,如果一个CPU核虚拟为两个CPU核工作,那么一台计算机上使用多个CPU核就是理所当然的事了,尽管如此 " 一个CPU执行的CPU命令列为一条无分叉的路径" 仍然 不变。这种无分叉的路径不止有一条,存在有多条时即为 "多线程"

    由于使用多线程的程序可以在摸个线程和其他线程之间反复多次进行上下文切换,因此看上去好像1个CPU核能够并列的执行多个线程一样,而且在具有多个CPU核的情况下,就不是 " 看上去像" 了,而是真的提供了多个CPU核并行执行多个线程的技术。

    这种利用多线程编程的技术就被称为"多线程编程"。

    GCD相关API

    1. Dispatch Queue

    开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中。

       dispatch_async(queue, ^{
    
           //想要执行的任务
       });
    

    该代码使用Block语法 “定义想执行的任务” ,通过dispatch_async函数 “追加” 赋值在变量queue的Dispatch Queue中,仅这样就可使指定的Block在另一个线程执行。
    Dispatch Queue是什么?如其名称所示,是执行处理的等待队列。应用程序编程人员通过dispatch_async函数等API,在Block语法中计述想执行的处理并追加到Dispatch Queue中。Dispatch Queue按追加顺序(先进先出FIFO,First-In-First-Out)执行处理。

    通过Dispatch Queue执行处理.png

    执行处理时存在两种Dispatch Queue,一种是等待现在执行中处理的Serial Dispatch Queue,另一种是不等待现在执行中处理的Concurrent Dispatch Queue

    Dispatch Queue种类 说明
    Serial Dispatch Queue (串行) 等待现在执行中处理结束
    Concurrent Dispatch Queue (并行) 不等待现在执行中处理结束

    👇下面看这个例子:

        dispatch_async(queue, ^{NSLog(@"block0");});
        dispatch_async(queue, ^{NSLog(@"block1");});
        dispatch_async(queue, ^{NSLog(@"block2");});
        dispatch_async(queue, ^{NSLog(@"block3");});
        dispatch_async(queue, ^{NSLog(@"block4");});
        dispatch_async(queue, ^{NSLog(@"block5");});
        dispatch_async(queue, ^{NSLog(@"block6");});
        dispatch_async(queue, ^{NSLog(@"block7");});
    

    1.当变量queueSerial Dispatch Queue时,因为要等待现在执行中的处理结束。首先执行block0,block0执行结束后,接着执行block1,block1执行结束后在执行block2,如此重复,同时执行的处理数只有1个。

    2.当变量queueConcurrent Dispatch Queue时,因为不用等待现在执行中的处理结束。所以首先执行block0,不管block0的执行是否结束,都开始执行后面的block1,不管block1的执行是否结束,都开始执行后面的block2,如此重复循环。

    如何创建Dispatch Queue,方法有两种。

    2. dispatch_queue_create

    第一种方法是通过 GCD 的API生成Dispatch Queue,通过dispatch_queue_create函数可以生成Dispatch Queue

        /**
         创建 dispatch_queue
         第一个参数: 线程名称,推荐使用应用程序ID这种逆序全程域名,也可以设置为`NULL`
         第二个参数: `SerialDispatchQueue`时设置为`DISPATCH_QUEUE_SERIAL` or `NULL`
                    `ConcurrentDispatchQueue`时设置为`DISPATCH_QUEUE_CONCURRENT`
         */
        dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("caoxueliang.MultiThreadStudy.mySerialDispatchQueue", NULL);
        dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("caoxueliang.MultiThreadStudy.myConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
        
        dispatch_async(mySerialDispatchQueue, ^{
            NSLog(@"block on mySerialDispatchQueue");
        });
        
        dispatch_async(myConcurrentDispatchQueue, ^{
            NSLog(@"block on myConcurrentDispatchQueue");
        });
    
    3. Main Dispatch Queue / Global Dispatch Queue

    第二种方法是获取系统标准的Dispatch Queue
    Main Dispatch Queue正如其名称中含有的Main一样,是在主线程中执行的Dispatch Queue,因为主线程只有一个,所以Main Dispatch Queue自然就是Serial Dispatch Queue
    追加到Main Dispatch Queue的处理在主线程的RunLoop中执行,因此要将用户界面更新等一些必须在主线程中执行的处理追加到Main Dispatch Queue使用。
    另一个Global Dispatch Queue是所有应用程序都能够使用的Concurrent Dispatch Queue,没有必要通过dispatch_queue_create函数逐个生成Concurrent Dispatch Queue,只要获取Global Dispatch Queue使用即可。

        //获取系统标准提供的 Dispatch Queue
        dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
        dispatch_async(mainDispatchQueue, ^{
            NSLog(@"主线程");
        });
        
        dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(globalDispatchQueue, ^{
            NSLog(@"globalDispatchQueue");
        });
    

    Global Dispatch Queue有4个执行优先级,通过XUN内核管理的用于Global Dispatch Queue的线程,将各自使用的Global Dispatch Queue的执行优先级作为线程的执行优先级使用。但是通过XUN内核用于Global Dispatch Queue的线程,并不能保证实时性,因此执行优先级只是大致的判断。

    Dispatch Queue的种类:

    名称 Dispatch Queue种类 说明
    Main Dispatch Queue Serial Dispatch Queue 主线程执行
    Global Dispatch Queue (High Priority) Global Dispatch Queue 执行优先级: 高
    Global Dispatch Queue (Default Priority) Global Dispatch Queue 执行优先级: 默认
    Global Dispatch Queue (Low Priority) Global Dispatch Queue 执行优先级: 低
    Global Dispatch Queue (Background Priority) Global Dispatch Queue 执行优先级: 后台

    Main Dispatch QueueGlobal Dispatch Queue结合使用的例子:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            /*
             * 可并行执行的处理
             *
            dispatch_async(dispatch_get_main_queue(), ^{
                //只能在主线程中执行的处理,更新UI
            });
        });
    
    4. dispatch_set_target_queue

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

       dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("caoxueliang.MultiThreadStudy.mySerialDispatchQueue", NULL);
       dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
        /*
         变更生成的Dispatch Queue 的执行优先级
         第一个参数: 要变更执行优先级的Dispatch Queue
         第二个参数: 指定与要使用的执行优先级相同优先级的`globalDispatchQueue`
         */
       dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueue);
    

    需要注意的是:第一个参数不能指定为系统提供的Main Dispatch QueueGlobal Dispatch Queue

    5. dispatch_after

    在指定的时间后执行处理,比如3秒后执行处理,可使用dispatch_after函数来实现。在3秒后将指定的Block,追加到Main Dispatch Queue中:

     dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC));
        dispatch_after(time, dispatch_get_main_queue(), ^{
            NSLog(@"waited at least three seconds");
      });
    

    需要注意的是,dispatch_after函数并不是在指定时间后执行处理,而只是在指定时间追加处理到Dispatch Queue,此代码与在三秒后用dispatch_async函数,追加Block到Main Dispatch Queue相同。

    因为Main Dispatch Queue在主线程的RunLoop中执行,所以在比如每隔1/60秒执行的RunLoop中,Block最快在3秒后执行,最慢在3秒+1/60秒后执行,并且在Main Dispatch Queue有大量处理追加或主线程的处理本身有延迟时,这个时间会更长。
    虽然在有严格时间的要求下使用时会出现问题,但在想大致延迟执行处理时,该函数是非常有效的。
    dispatch_time函数通常用于计算相对时间,而dispatch_walltime函数用于计算绝对时间,例如在dispatch_after函数中指定2011年11月11日11分11秒这一绝对时间的情况。

    由NSDate类对象获取传递给dispatch_after函数的dispatch_time_t类型的值:

    static inline dispatch_time_t dispatch_walltime_date(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;
    }
    
    6. Dispatch Group

    在追加到Dispatch Queue中的多个处理全部结束后想执行结束处理,这种情况会经常出现,只是用一个Serial Dispatch Queue时,只要将想执行的处理全部追加到该Serial Dispatch Queue中并在最后追加结束处理,即可实现,但是使用Concurrent Dispatch Queue时或同时使用多个Dispatch Queue时,源代码就会变得非常复杂。

    这种情况下应该使用Dispatch Group,例如下载3张图片,只有当这3张图片都下载完成时,才会走结束处理的Block。

        /*
         在追加到 Dispatch Queue 中的多个处理全部结束后,执行结束处理
         */
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_group_t group = dispatch_group_create();
        dispatch_group_async(group, queue, ^{
            NSURL *imageUrl = [NSURL URLWithString:@"https://wx1.sinaimg.cn/mw690/9bbc284bgy1flt5w1kf5gj20dw0ku13h.jpg"];
            NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
            if (imageData) {
                NSLog(@"block1");
            }
        });
        dispatch_group_async(group, queue, ^{
            NSURL *imageUrl = [NSURL URLWithString:@"https://wx3.sinaimg.cn/mw690/9bbc284bgy1fly7dmgh87j20gq0r6akh.jpg"];
            NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
            if (imageData) {
                NSLog(@"block2");
            }
        });
        dispatch_group_async(group, queue, ^{
            NSURL *imageUrl = [NSURL URLWithString:@"https://wx3.sinaimg.cn/mw690/9bbc284bgy1fly7dmgh87j20gq0r6akh.jpg"];
            NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
            if (imageData) {
                NSLog(@"block3");
            }
        });
        dispatch_group_notify(group, queue, ^{
            NSLog(@"执行完毕");
        });
    

    上面代码的执行结果为:

    2017-12-06 23:28:01.661591+0800 MultiThreadStudy[1329:81841] block1
    2017-12-06 23:28:01.802706+0800 MultiThreadStudy[1329:81846] block3
    2017-12-06 23:28:01.886015+0800 MultiThreadStudy[1329:81843] block2
    2017-12-06 23:28:01.886432+0800 MultiThreadStudy[1329:81843] 执行完毕
    

    因为向Global Dispatch QueueConcurrent Dispatch Queue追加处理,多个线程并行执行,所以追加处理的执行顺序不定,执行时会发生变化,但是最后执行完毕一定是最后输出的。

    7. Dispatch_barrier_async

    在访问数据库或文件时,如上所述,使用Serial Dispatch Queue可避免数据竞争的问题,写入处理确实不可与其他的写入处理以及包含读取处理的其他某些处理并行执行,但是如果读取处理只是与读取处理并行执行,那么多个并行执行就不会发生问题。
    也就是说,为了高效率的进行访问,读取处理追加到Concurrent Dispatch Queue中,写入处理在任一个读取处理没有执行的状态下,追加到Serial Dispatch Queue中即可(在写入处理结束之前,读取处理不可执行)。
    Dispatch_barrier_async函数同dispatch_queue_create函数生成的Concurrent Dispatch Queue一起使用。

    在block3_for_reading处理和block4_for_reading处理之间执行写入处理,并将写入的内容读取block4_for_reading处理以及之后的处理中。

        dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.ForBarrier", DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(queue, ^{sleep(4); NSLog(@"block0_for_reading");});
        dispatch_async(queue, ^{sleep(1); NSLog(@"block1_for_reading");});
        dispatch_async(queue, ^{sleep(2); NSLog(@"block2_for_reading");});
        dispatch_async(queue, ^{sleep(2); NSLog(@"block3_for_reading");});
    
        dispatch_async(queue, ^{sleep(3);NSLog(@"写入处理");});
    
        dispatch_async(queue, ^{sleep(1); NSLog(@"block4_for_reading");});
        dispatch_async(queue, ^{sleep(2); NSLog(@"block5_for_reading");});
        dispatch_async(queue, ^{sleep(4); NSLog(@"block6_for_reading");});
        dispatch_async(queue, ^{NSLog(@"block7_for_reading");});
    

    运行结果如下所示:

    2017-12-11 22:36:10.379768+0800 MultiThreadStudy[758:22271] block7_for_reading
    2017-12-11 22:36:11.381917+0800 MultiThreadStudy[758:22260] block1_for_reading
    2017-12-11 22:36:11.381963+0800 MultiThreadStudy[758:22268] block4_for_reading
    2017-12-11 22:36:12.382093+0800 MultiThreadStudy[758:22261] block2_for_reading
    2017-12-11 22:36:12.382097+0800 MultiThreadStudy[758:22257] block3_for_reading
    2017-12-11 22:36:12.382124+0800 MultiThreadStudy[758:22269] block5_for_reading
    2017-12-11 22:36:13.382211+0800 MultiThreadStudy[758:22267] 写入处理
    2017-12-11 22:36:14.382834+0800 MultiThreadStudy[758:22258] block0_for_reading
    2017-12-11 22:36:14.382854+0800 MultiThreadStudy[758:22270] block6_for_reading
    

    如果像上面👆这样简单的在dispatch_async函数中加入写入处理,那么根据Concurrent Dispatch Queue的性质,就有可能在追加到写入处理前面的处理中读取到与期待不符的数据,还可能因非法访问导致应用程序异常结束。如果追加多个写入处理,则可能发生更多问题,比如数据竞争等。

    所以,应该使用dispatch_barrier_async函数代替dispatch_async函数进行写入处理,如下所示:

        dispatch_async(queue, ^{sleep(4); NSLog(@"block0_for_reading");});
        dispatch_async(queue, ^{sleep(1); NSLog(@"block1_for_reading");});
        dispatch_async(queue, ^{sleep(2); NSLog(@"block2_for_reading");});
        dispatch_async(queue, ^{sleep(2); NSLog(@"block3_for_reading");});
    
        dispatch_barrier_async(queue, ^{
            NSLog(@"写入处理");
        });
    
        dispatch_async(queue, ^{sleep(1); NSLog(@"block4_for_reading");});
        dispatch_async(queue, ^{sleep(2); NSLog(@"block5_for_reading");});
        dispatch_async(queue, ^{sleep(4); NSLog(@"block6_for_reading");});
        dispatch_async(queue, ^{NSLog(@"block7_for_reading");});
    

    运行结果如下:

    2017-12-11 22:52:40.062396+0800 MultiThreadStudy[834:30834] block1_for_reading
    2017-12-11 22:52:41.062253+0800 MultiThreadStudy[834:30832] block2_for_reading
    2017-12-11 22:52:41.062253+0800 MultiThreadStudy[834:30835] block3_for_reading
    2017-12-11 22:52:43.062270+0800 MultiThreadStudy[834:30831] block0_for_reading
    2017-12-11 22:52:43.062679+0800 MultiThreadStudy[834:30831] 写入处理
    2017-12-11 22:52:43.063032+0800 MultiThreadStudy[834:30834] block7_for_reading
    2017-12-11 22:52:44.063647+0800 MultiThreadStudy[834:30831] block4_for_reading
    2017-12-11 22:52:45.065397+0800 MultiThreadStudy[834:30835] block5_for_reading
    2017-12-11 22:52:47.065416+0800 MultiThreadStudy[834:30832] block6_for_reading
    

    因此我们要使用dispatch_barrier_async函数,该函数会等待追加到Concurrent Dispatch Queue上的并行执行的处理全部结束之后,再将指定的处理追加到该Dispatch Dispatch Queue中,然后在由dispatch_barrier_async函数追加的处理执行完毕后,Concurrent Dispatch Queue才恢复为一般的动作,追加到该Concurrent Dispatch Queue的处理又开始并行执行。

    Dispatch_barrier_async函数处理流程.png
    8. dispatch_sync 与 dispatch_async

    dispatch_async函数的async意味着非同步,就是将指定的Block非同步地追加到指定的Dispatch_Queue中,dispatch_async函数不做任何等待,不等待处理执行结束。
    既然有async,当然也就有sync,即dispatch_sync函数,它意味着同步,也就是将指定的Block同步追加到指定的Dispatch Queue中,在追加Block之前,dispatch_sync函数会一直等待。一旦调用dispatch_sync函数使用简单,所以也容易引起问题,即死锁。

    下面👇这段代码表示在Main Dispatch Queue即主线程中执行指定的Block,并等待其执行结束,而其实在主线程中正在执行这些源代码,所以无法执行追加到Main Dispatch Queue的Block。

    dispatch_queue_t queue = dispatch_get_main_queue();
        dispatch_async(queue, ^{
            dispatch_sync(queue, ^{
                 NSLog(@"hello");
            });
        });
    
    9. dispatch_apply

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

       //推荐在`dispatch_async`函数中非同步的执行`dispatch_apply`函数
        NSArray *tmpArray = [NSArray arrayWithObjects:@1,@2,@3,@4, nil];
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(queue, ^{
            /*
             *Global Dispatch Queue
             *等待`dispatch_apply`函数中全部处理执行结束
             */
            dispatch_apply([tmpArray count], queue, ^(size_t index) {
                
                //并列处理包含在`Nsarray`中的全部对象
                NSLog(@"%@",[tmpArray objectAtIndex:index]);
            });
            
            //`dispatch_apply`函数中处理全部执行结束
            
            //在`main dispatch queue`中非同步执行
            dispatch_async(dispatch_get_main_queue(), ^{         
                //更新用户界面
                NSLog(@"done");
            });
        });
    

    输出结果为:

    2017-12-13 21:06:55.070579+0800 MultiThreadStudy[935:29821] 2
    2017-12-13 21:06:55.070579+0800 MultiThreadStudy[935:29813] 1
    2017-12-13 21:06:55.070580+0800 MultiThreadStudy[935:29820] 3
    2017-12-13 21:06:55.070604+0800 MultiThreadStudy[935:29817] 4
    2017-12-13 21:06:55.075021+0800 MultiThreadStudy[935:29629] done
    

    因为在Global Dispatch Queue中执行处理,所以各个处理的执行时间不定,但输出结果中最后的done必定在最后的位置,这是因为diapatch_apply函数会等待全部处理执行结束。
    第一个参数为重复次数,第二个参数为追加对象的Dispatch Queue,第三个参数的Block为带参数的Block。
    另外,由于dispatch_apply函数也与dispatch_sync函数相同,会等待处理执行结束,因此推荐在dispatch_async函数中非同步地执行dispatch_apply函数。

    10. dispatch_suspend / dispatch_resume

    当追加大量处理到Dispatch Queue时,在追加处理的过程中,有时希望不执行已追加的处理,在这种情况下,只要挂起Dispatch Queue即可,当可以执行时在恢复。

    //挂起指定的queue
    dispatch_suspend(queue);
    
    //恢复指定的queue
    dispatch_resume(queue);
    

    这些函数对已经执行的处理没有影响,挂起后,追加到Disaptch Queue中但尚未执行的处理在此之后停止执行,而恢复则使得这些处理能够继续执行。

    11. dispatch_semaphore

    信号量就是一种可用来控制访问资源的数量标识,设定一个信号量,在线程访问之前,加上信号量的处理,则告知系统按照我们指定的信号量数量来执行多个线程。

    • dispatch_semaphore_create(n) :生成信号,n表示信号量为n。
    • dispatch_semaphore_wait:信号等待,它像一个安保,比如小区规定最多只能进入3辆车,而进入一辆车后名额就会减少一个,当剩下的名额为0的时候,再有汽车说要进去时,就只能在外面等待了,直到有名额闲置出来了,才能开进小区。
    • dispatch_semaphore_signal:信号释放,当有一辆车从小区出来时,就腾出来了一个名额。
    /*
         * 生成 Dispatch Semaphone
         * Dispatch Semaphone 的计数初始值设置为2
         */
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
        dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        //任务1
        dispatch_async(quene, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"run task 1");
            sleep(1);
            NSLog(@"complete task 1");
            dispatch_semaphore_signal(semaphore);
        });
        //任务2
        dispatch_async(quene, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"run task 2");
            sleep(1);
            NSLog(@"complete task 2");
            dispatch_semaphore_signal(semaphore);
        });
        //任务3
        dispatch_async(quene, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"run task 3");
            sleep(1);
            NSLog(@"complete task 3");
            dispatch_semaphore_signal(semaphore);
        });
    

    打印结果如下:

    2017-12-13 23:03:00.196126+0800 MultiThreadStudy[1284:98022] run task 2
    2017-12-13 23:03:00.196126+0800 MultiThreadStudy[1284:98014] run task 1
    2017-12-13 23:03:01.201554+0800 MultiThreadStudy[1284:98022] complete task 2
    2017-12-13 23:03:01.201562+0800 MultiThreadStudy[1284:98014] complete task 1
    2017-12-13 23:03:01.201911+0800 MultiThreadStudy[1284:98015] run task 3
    2017-12-13 23:03:02.205812+0800 MultiThreadStudy[1284:98015] complete task 3
    

    当将信号量个数设置为1时,打印结果如下:

    2017-12-13 23:04:02.907501+0800 MultiThreadStudy[1316:99128] run task 1
    2017-12-13 23:04:03.912940+0800 MultiThreadStudy[1316:99128] complete task 1
    2017-12-13 23:04:03.913360+0800 MultiThreadStudy[1316:99131] run task 2
    2017-12-13 23:04:04.913853+0800 MultiThreadStudy[1316:99131] complete task 2
    2017-12-13 23:04:04.914204+0800 MultiThreadStudy[1316:99129] run task 3
    2017-12-13 23:04:05.919195+0800 MultiThreadStudy[1316:99129] complete task 3
    

    当将信号量个数设置为3时,则打印结果如下:

    2017-12-13 23:05:22.677144+0800 MultiThreadStudy[1354:100642] run task 1
    2017-12-13 23:05:22.677145+0800 MultiThreadStudy[1354:100638] run task 2
    2017-12-13 23:05:22.677175+0800 MultiThreadStudy[1354:100646] run task 3
    2017-12-13 23:05:23.681331+0800 MultiThreadStudy[1354:100642] complete task 1
    2017-12-13 23:05:23.681333+0800 MultiThreadStudy[1354:100646] complete task 3
    2017-12-13 23:05:23.681333+0800 MultiThreadStudy[1354:100638] complete task 2
    

    通过上面的例子,对dispatch_semaphore已经有了基本的了解。

    12. dispatch_once

    dispatch_once函数是保证在应用程序执行中只执行一次指定处理的API。请看下面👇这个例子:

    + (NSBundle *)bundle {
        static NSBundle *bundle;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSString *path = [[NSBundle mainBundle] pathForResource:@"ResourceWeibo" ofType:@"bundle"];
            bundle = [NSBundle bundleWithPath:path];
        });
        return bundle;
    }
    

    通过dispatch_once函数创建的,该代码即使在多线程环境下执行,也可以保证百分百安全。该函数经常在生成单例对象时使用。

    13. dispatch_source

    dispatch_source函数可以实现定时器的功能:

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        //每秒执行
        dispatch_source_set_timer(_timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0);
    
        //指定定时器指定时间内执行的处理
        dispatch_source_set_event_handler(_timer, ^{
            NSLog(@"text");
            if(time <= 0){
                //倒计时结束,关闭
                dispatch_source_cancel(_timer);
                dispatch_async(dispatch_get_main_queue(), ^{
    
                });
            }
        });
        //启动 Dispatch Source
        dispatch_resume(_timer);
    

    结尾

    参考书籍《Objective-C高级编程》
    文中DemoGitHub下载

    相关文章

      网友评论

        本文标题:《Objective-C高级编程》多线程编程GCD详解

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