iOS多线程开发之GCD(中级篇)

前文回顾:

      上篇博客讲到GCD的实现是由队列和任务两部分组成,其中获取队列的方式有两种,第一种是通过GCD的API的dispatch_queue_create函数生成Dispatch Queue;第二种是直接使用系统提供的标准Dispatch Queue :Main Dispatch Queue和Global Dispatch Queue,具体的实现方式请参照上篇博客《iOS多线程开发之GCD(上篇)》。

 

这篇博客主要讲解以下苹果提供的一些常用GCD和代码示例及其注意点。

  • dispatch_set_target_queue
  • dispatch_after
  • dispatch_once / dispatch_apply
  • Dispatch Group
  • dispatch_barrier_sync
  • dispatch_suspend / dispatch_resume
  • Dispatch Semaphore

一、dispatch_set_target_queue 

        dispatch_set_target_queue中涉及的代码示例来源于网络

        1、dispatch_set_target_queue 更改Dispatch Queue的执行优先级

        dispatch_queue_create函数生成的DisPatch Queue不管是Serial DisPatch Queue还是Concurrent Dispatch Queue,执行的优先级都与默认优先级的Global Dispatch queue相同,如果需要变更生成的Dispatch Queue的执行优先级则需要使用dispatch_set_target_queue函数。

dispatch_queue_t serialQueue = dispatch_queue_create("com.beckwang.www",NULL);

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
 
// 第一个参数为要变更优先级的queue,第二个参数是参照物,既将第一个queue的优先级和第二个queue的优先级设置一样。
dispatch_set_target_queue(serialQueue, globalQueue);

       2、dispatch_set_target_queue作为执行阶层,修改队列的目标队列使多个serial queue在目标queue上一次只有一个执行

       
      第一种情况:使用dispatch_set_target_queue(Dispatch Queue1, Dispatch Queue2)实现队列的动态调度管理
- (void)testTargetQueue2 {
    //创建一个串行队列queue1
    dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
    //创建一个串行队列queue2
    dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL);
    
    //使用dispatch_set_target_queue()实现队列的动态调度管理
    dispatch_set_target_queue(queue1, queue2);
    
    /*
     那么dispatchA上还未运行的block会在dispatchB上运行。这时如果暂停dispatchA运行:
     dispatch_suspend(dispatchA);
     这时则只会暂停dispatchA上原来的block的执行,dispatchB的block则不受影响。而如果暂停dispatchB的运行,则会暂停dispatchA的运行。
     dispatch队列不支持cancel(取消),没有实现dispatch_cancel()函数,不像NSOperationQueue,不得不说这是个小小的缺憾
     */

    dispatch_async(queue1, ^{
        for (NSInteger i = 0; i < 10; i++) {
            NSLog(@"queue1:%@, %ld", [NSThread currentThread], i);
            [NSThread sleepForTimeInterval:0.5];
            if (i == 5) {
                dispatch_suspend(queue2);
            }
        }
    });
    
    dispatch_async(queue1, ^{
        for (NSInteger i = 0; i < 100; i++) {
            NSLog(@"queue1:%@, %ld", [NSThread currentThread], i);
        }
        
    });
    
    dispatch_async(queue2, ^{
        for (NSInteger i = 0; i < 100; i++) {
            NSLog(@"queue2:%@, %ld", [NSThread currentThread], i);
        }
    });
}
View Code
      第二种情况:使用dispatch_set_target_queue将多个串行的queue指定到了同一目标,那么着多个串行queue在目标queue上就是一次只能执行一个(化并行为串行)。

        适用场景:一般都是把一个任务放到一个串行的queue中,如果这个任务被拆分了,被放置到多个串行的queue中,但实际还是需要这个任务同步执行,那么就会有问题,因为多个串行queue之间是并行的。这时候dispatch_set_target_queue将起到作用。

      (1)没有使用dispatch_set_target_queue时:

- (void)testTargetQueue3 {
    //1.创建目标队列
    //dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL);
    
    //2.创建3个串行队列
    dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_SERIAL);
    
    //3.将3个串行队列分别添加到目标队列
    //dispatch_set_target_queue(queue1, targetQueue);
    //dispatch_set_target_queue(queue2, targetQueue);
    //dispatch_set_target_queue(queue3, targetQueue);
    
    dispatch_async(queue1, ^{
        NSLog(@"1 in");
        [NSThread sleepForTimeInterval:3.f];
        NSLog(@"1 out");
    });
    
    dispatch_async(queue2, ^{
        NSLog(@"2 in");
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"2 out");
    });
    dispatch_async(queue3, ^{
        NSLog(@"3 in");
        [NSThread sleepForTimeInterval:1.f];
        NSLog(@"3 out");
    });
}

      打印结果:

2017-07-04 19:52:51.915 Test[5759:927698] 1 in
2017-07-04 19:52:51.915 Test[5759:927699] 2 in
2017-07-04 19:52:51.915 Test[5759:927701] 3 in
2017-07-04 19:52:52.916 Test[5759:927701] 3 out
2017-07-04 19:52:53.921 Test[5759:927699] 2 out
2017-07-04 19:52:54.919 Test[5759:927698] 1 out 

     结论:多个串行queue之间是并行的!

   (2)使用dispatch_set_target_queue设置target

- (void)testTargetQueue3 {
    //1.创建目标队列
    dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL);
    
    //2.创建3个串行队列
    dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_SERIAL);
    
    //3.将3个串行队列分别添加到目标队列
    dispatch_set_target_queue(queue1, targetQueue);
    dispatch_set_target_queue(queue2, targetQueue);
    dispatch_set_target_queue(queue3, targetQueue);
    
    
    dispatch_async(queue1, ^{
        NSLog(@"1 in");
        [NSThread sleepForTimeInterval:3.f];
        NSLog(@"1 out");
    });
    
    dispatch_async(queue2, ^{
        NSLog(@"2 in");
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"2 out");
    });
    dispatch_async(queue3, ^{
        NSLog(@"3 in");
        [NSThread sleepForTimeInterval:1.f];
        NSLog(@"3 out");
    });
}

      打印结果:

2017-07-04 19:58:33.667 Test[5830:968024] 1 in
2017-07-04 19:58:36.672 Test[5830:968024] 1 out
2017-07-04 19:58:36.672 Test[5830:968024] 2 in
2017-07-04 19:58:38.678 Test[5830:968024] 2 out
2017-07-04 19:58:38.679 Test[5830:968024] 3 in
2017-07-04 19:58:39.683 Test[5830:968024] 3 out

     结论:多个串行queue之间是串行的!

 

二、dispatch_after

     如果需要延时处理某件事情,我们可以使用dispatch_after,需要注意的是dispatch_after并不是将任务追加到队列dispatch_queue后再根据时间参数延迟执行block代码,而是在指定时间后追加任务到到dispatch_queue。代码示例:

dispatch_time_t time=dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"这里是dispatch_after测试");
});

//
dispatch_get_main_queue ---> diapatch_get_gloab_queue 就可以更改执行线程

     实现延时处理除了上面的GCD(dispatch_after)外,还可以通过以下方法:

     (1)performSelector(NSObject)方法:

// 不带参数
[self performSelector:@selector(doSomething) withObject:self afterDelay:3.0f];

// 带参数
[self performSelector:@selector(delayDo:) withObject:@"paramtest" afterDelay:3.0f];

// 取消全部
[NSObject cancelPreviousPerformRequestsWithTarget:self];

// 取消不传参的方法
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(delayDo:) object:nil];

// 取消传参的方法
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(delayDo:) object:@"paramtest"];

    (2)NSTimer的类方法:

// 不带参数
[NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(doSomething) userInfo:nil repeats:NO];

// 带参数
[NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(doSomething) userInfo:@"paramtest" repeats:NO];

       使用NSTimer时注意事项可以参考我的另外一篇博客《实现定时器NSTimer的高逼格方法

     (3)sleep(NSThreed)

[NSThread sleepForTimeInterval:1.0f];
// 这里执行延迟处理代码
[self doSomething];

 

三、dispatch_once 和 dispacth_apply

     dispatch_once整个app运行周期内只执行一次代码,多用于单例模式。

dispatch_once_t *predicate:一个全局的变量   dispatch_block_t block:block函数块     

     dispatch_apply让指定代码按设定次数多次执行,dispatch_apply类似一个for循环,会在指定的dispatch queue中运行block任务n次,如果队列是并发队列,则会并发执行block任务,如果队列是串行队列,则block任务只会同步执行,但是dispatch_apply是一个同步调用,block任务执行n次后才返回。

size_t iterations:执行次数  dispatch_queue_t queue:队列   void (^block)(size_t):block函数块

   代码示例:

 (1)dispatch_once 

   自定义block函数块

//定义block
typedef void (^BLOCK)(void);
    
//将执行代码封装到block中
BLOCK myBlock = ^(){
    static int count = 0;
    NSLog(@"count=%d",count++);
};

  执行

// 只会执行一次
static dispatch_once_t predicate;
dispatch_once(&predicate, myBlock);

 打印结果:count  = 0;

 

  (2) dispatch_apply

  自定义block

//定义block
typedef void (^BLOCK)(size_t);
    
//将函数封装到block
BLOCK myBlock = ^(size_t size){
    static int count = 0;
    NSLog(@"count=%d",count++);      
};

  执行

dispatch_apply(5, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), myBlock);

  打印结果:

count = 0
count = 2
count = 3
count = 1
count = 4

    显而易见,如果dispatch_apply的队列是自定义的串行队列

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

    输出结果将是:

count = 0
count = 1
count = 2
count = 3
count = 4

   dispatch_apply 可以处理一个任务重复执行次数量级比较大的应用场景,假设从服务器上获取一组数组数据(超过100个元素对象)然后进行字典转化模型

   多线程并发处理:

// 多线程并发处理,可能造成线程爆炸及死锁
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);

for (int i = 0; i < 999; i++){
   dispatch_async(queue, ^{
         // 字典转模型 
   });
}
dispatch_barrier_sync(dispatch_get_main_queue(), ^{
       NSLog(@"主线程更新");
});

---------------------------这里是分割线---------------------------

// dispatch_apply 方式 (优先选择)
NSArray *dictArray = nil;

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
   
 dispatch_async(queue, ^{

        dispatch_apply(dictArray.count, queue,  ^(size_t index){
            //字典转模型

        });

        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"主线程更新");
        });
    });

 

四、Dispatch Group

         在追加到Dispatch Queue中的多个任务处理全部完毕之后想执行结束处理。如果只是使用一个Serial Dispatch Queue(串行队列)时,只要将想执行的处理全部追加到该串行队列中并在最后追加结束处理即可,但是在使用Concurrent Queue 时,可能会同时使用多个Dispatch Queue时,这就需要使用Dispatch Group。

- (void)testDispatchGroup{
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("com.gcdgroup.www", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 10; i++) {
            if (i == 9) {
                NSLog(@"test001");
            }
        }
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"test002");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"test003");
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"全部完成");
    });
}

     打印结果:

2017-07-06 23:30:57.449 Test[8724:1743565] test002
2017-07-06 23:30:57.449 Test[8724:1743547] test003
2017-07-06 23:30:57.449 Test[8724:1743549] test001
2017-07-06 23:30:57.449 Test[8724:1743547] 全部完成

    Dispatch Group广泛运用到异步获取网络数据最后汇总的情况,如异步获取多张网络图片资源后拼接成一张图片等等。

 

五、dispatch_barrier_async

    在访问数据库和文件时,如前所述,使用Serial Dispatch Queue可避免数据资源的竞争问题。众所周知,写处理与写处理,写处理与读处理会发生数据一致性或数据竞争问题,但是读处理与读处理之前不存在数据一致性问题,为了提高效率我们可以这样设想:读处理可以追加到Concurrent Dispatch Queue(并发队列)中,而写处理在任意一个没有读取处理执行的状态下追加到Serial Dispatch Queue(串行队列)中(在写处理结束之前,读处理不可执行)。

代码示例:

- (void)testDispatchBarrier{
    
    dispatch_queue_t queue = dispatch_queue_create("com.gcdbarrier.www", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        
        NSLog(@"block001_read");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"block002_read");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"block003_read");
        
    });
    
    dispatch_barrier_sync(queue, ^{
        
        NSLog(@"block004_write");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"block005_read");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"block006_read");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"block007_read");
    });
    
}

   打印结果:

2017-07-06 23:59:27.936 Test[9080:1907162] block003_read
2017-07-06 23:59:27.936 Test[9080:1907194] block002_read
2017-07-06 23:59:27.936 Test[9080:1907163] block001_read
2017-07-06 23:59:27.937 Test[9080:1907028] block004_write
2017-07-06 23:59:27.937 Test[9080:1907163] block005_read
2017-07-06 23:59:27.937 Test[9080:1907162] block007_read
2017-07-06 23:59:27.937 Test[9080:1907194] block006_read

 

 

六、dispatch_suspend / dispatch_resume

      dispatch_suspend,dispatch_resume提供了“挂起、恢复”队列的功能,简单来说,就是可以暂停、恢复队列上的任务。但是这里的“挂起”,并不能保证可以立即停止队列上正在运行的block,而是在当前block执行完成后,暂停后续的block执行。

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

      代码示例:

- (void)gcdSuspendResume{

    dispatch_queue_t queue = dispatch_queue_create("com.test.gcd", DISPATCH_QUEUE_SERIAL);
    // 提交第一个block,延时5秒打印。
    dispatch_async(queue, ^{
        sleep(5);
        NSLog(@"After 5 seconds...");
    });
    // 提交第二个block,也是延时5秒打印
    dispatch_async(queue, ^{
        sleep(5);
        NSLog(@"After 5 seconds again...");
    });
    // 延时一秒
    NSLog(@"sleep 1 second...");
    sleep(1);
    // 挂起队列
    NSLog(@"suspend...");
    dispatch_suspend(queue);
    // 延时10秒
    NSLog(@"sleep 10 second...");
    sleep(10);
    // 恢复队列
    NSLog(@"resume...");
    dispatch_resume(queue);
}

     打印结果:

2017-07-07 14:29:44.329 beck.wang[1045:77001] sleep 1 second...
2017-07-07 14:29:45.330 beck.wang[1045:77001] suspend...
2017-07-07 14:29:45.330 beck.wang[1045:77001] sleep 10 second...
2017-07-07 14:29:49.333 beck.wang[1045:77045] After 5 seconds...
2017-07-07 14:29:55.331 beck.wang[1045:77001] resume...
2017-07-07 14:30:00.336 beck.wang[1045:77045] After 5 seconds again...

 

七、Dispatch Semaphore 

       dispatch_semaphore(信号量)是基于计数器的一种多线程同步机制,是GCD控制并发的一种方式。

       信号量是一个整形值并且具有一个初始计数值,并且支持两个操作:信号通知和等待。当一个信号量被信号通知,其计数会被增加。当一个线程在一个信号量上等待时,线程会被阻塞(如果有必要的话),直至计数器大于零,然后线程会减少这个计数。

      在GCD中有三个函数是semaphore的操作,分别是:dispatch_semaphore_create、dispatch_semaphore_signal、dispatch_semaphore_wait。

      1、dispatch_semaphore_create  创建具有初始值的信号量

// 输出一个dispatch_semaphore_t类型且值为value的信号量。这里的传入的参数value必须>=0,否则dispatch_semaphore_create会返回NULL。
dispatch_semaphore_t 
dispatch_semaphore_create(long value);

// 示例
dispatch_semaphore_t  semaphore = dispatch_semaphore_create(1);

 

     2、dispatch_semaphore_signal  发送信号量,让信号量总数+1

long dispatch_semaphore_signal(dispatch_semaphore_tdsema) 

     3、dispatch_semaphore_wait    等待信号量,当信号总量< 0 的时候等待设置的timeout参数,否则就可以正常的执行,并让信号总量 -1

long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

 

     注意:timeout是dispatch_time_t类型,不可直接使用其他类型(int或者float等)。如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,且dispatch_semaphore_wait所处线程获得了信号量,那么就继续向下执行并将信号量减1;如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。

    苹果给了两个timeout的宏定义,也是比较常用的。

DISPATCH_TIME_NOW  //当前时间
DISPATCH_TIME_FOREVER // 一直等待

 

   如果需要自定义timeout可以使用

dispatch_time_t
dispatch_time(dispatch_time_t when, int64_t delta);

dispatch_time_t
dispatch_walltime(const struct timespec *_Nullable when, int64_t delta);

 

喜欢我的文章请点击关注哦,我将在以后的工作中争取写出更高质量的博客,交流分享!……^_^

posted @ 2017-07-07 16:36  贝克的飞机  阅读(1438)  评论(0编辑  收藏  举报