iOS多线程- GCD详解(二)

前言

该篇介绍并发,串行,并行,异步和同步详解和使用,以及其他方法,在理解多线程之前,必须先知道这几个概念。

并发: 单核设备,执行多个线程操作,并非同步执行,既当有一个线程在执行时,其他线程处于挂起状态,会相互抢占资源,或者一个线程中,不同队列,在一个队列中执行时其他队列阻塞。
并行:多核设备,执行多个线程操作,并且同时执行,不会相互抢占资源。
串行:任务在同一个线程执行,且执行完一个才能执行下一个
同步:不开辟新的线程,在同一个线程按顺序执行
异步:开辟新的线程,在新的线程中执行

1 、异步和同步执行的方法介绍

dispatch_async(dispatch_queue_t queue, dispatch_block_t block)
//  在调度队列上提交异步执行块并立即返回。
    
dispatch_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work)
// 以在调度队列上进行异步执行并立即返回到自己定义的方法中。
    
 dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block)
// 将块排队以在指定时间执行。
    
dispatch_after_f(dispatch_time_t when, dispatch_queue_t queue, void *context, dispatch_function_t work)
// 将应用程序定义的函数排入队列,以便在指定时间执行。
    
dispatch_function_t
// 提交给派遣队列的函数原型。
    
dispatch_block_t
// 提交给调度队列的块的原型,它不带参数且没有返回值。

dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
// 提交块对象以便执行,并在该块完成同步执行后返回。

dispatch_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work)
// 提交应用程序定义的函数以在调度队列上同步执行同步。

dispatch_queue_get_label
// 获取队列的标签

2 、异步和同步执行的实现

在应用过程中,同步和异步,并行和串行是组合使用的,并且需要结合是在主线程还是全局线程。我们对各种情况进行分析。

  • 串行(DISPATCH_QUEUE_SERIAL)
  • 并行(DISPATCH_QUEUE_CONCURRENT)
  • dispatch_queue_global(全局队列,并发执行)
  • dispatch_queue_main(主队列,串行执行)
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_SERIAL);
// 创建串行队列
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);
// 创建并发队列
同步+串行

即不开辟新的线程,在同一个线程中,按顺序执行。当执行完一个之后才能执行下一个。

dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);
        
dispatch_sync(queue, ^{
    NSLog(@"线程:1>>>%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
    NSLog(@"线程:2>>>%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
    NSLog(@"线程:3>>>%@",[NSThread currentThread]);
});

NSLog(@"线程:4>>>%@",[NSThread currentThread]);

打印之后的结果为:

2019-04-16 16:38:37.974577+0800[24573:3900925] 线程:1>>><NSThread: 0x100703540>{number = 1, name = main}
2019-04-16 16:38:37.974789+0800[24573:3900925] 线程:2>>><NSThread: 0x100703540>{number = 1, name = main}
2019-04-16 16:38:37.974810+0800[24573:3900925] 线程:3>>><NSThread: 0x100703540>{number = 1, name = main}
2019-04-16 16:38:37.974827+0800[24573:3900925] 线程:4>>><NSThread: 0x100703540>{number = 1, name = main}

可以看到,线程是顺序执行的,且都在主线程中执行,没有开辟线程,验证我们的结论。

同步+并行

即不开辟新的线程,在同一个线程中,只有一个线程,也得按顺序一个接一个,因为本身就不能多创建线程,也就不存在并发。

dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);
        
dispatch_sync(queue, ^{
    NSLog(@"线程:1>>>%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
    NSLog(@"线程:2>>>%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
    NSLog(@"线程:3>>>%@",[NSThread currentThread]);
});
NSLog(@"线程:4>>>%@",[NSThread currentThread]);
2019-04-16 17:05:26.950802+0800 block_test[25020:3949022] 线程:1>>><NSThread: 0x100503b00>{number = 1, name = main}
2019-04-16 17:05:26.951073+0800 block_test[25020:3949022] 线程:2>>><NSThread: 0x100503b00>{number = 1, name = main}
2019-04-16 17:05:26.951094+0800 block_test[25020:3949022] 线程:3>>><NSThread: 0x100503b00>{number = 1, name = main}
2019-04-16 17:05:26.951146+0800 block_test[25020:3949022] 线程:4>>><NSThread: 0x100503b00>{number = 1, name = main}

可以看到,都是在同一个线程顺序执行

异步+串行

即会开辟且只开辟一个新的线程,并且在新的线程中按顺序一个一个执行,并且与旧线程并行执行

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

dispatch_async(queue, ^{
    NSLog(@"线程:1>>>%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"线程:2>>>%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"线程:3>>>%@",[NSThread currentThread]);
});
NSLog(@"线程:4>>>%@",[NSThread currentThread]);
2019-04-16 17:18:32.885431+0800[25146:3971063] 线程:1>>><NSThread: 0x102812890>{number = 2, name = (null)}
2019-04-16 17:18:32.885464+0800[25146:3971032] 线程:4>>><NSThread: 0x100506350>{number = 1, name = main}
2019-04-16 17:18:32.885637+0800[25146:3971063] 线程:2>>><NSThread: 0x102812890>{number = 2, name = (null)}
2019-04-16 17:18:32.885658+0800[25146:3971063] 线程:3>>><NSThread: 0x102812890>{number = 2, name = (null)}

可以看到,结果开辟了且只有一个线程,在这个线程中顺序执行,并且与主线程并行执行,在主线程之后。

异步+并行

即会开辟且只开辟一个新的线程,并且在新的线程中按顺序一个一个执行,并且与旧线程并行执行

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

dispatch_async(queue, ^{
    NSLog(@"线程:1>>>%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"线程:2>>>%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"线程:3>>>%@",[NSThread currentThread]);
});
NSLog(@"线程:4>>>%@",[NSThread currentThread]);
2019-04-16 17:10:51.809839+0800[25074:3958365] 线程:4>>><NSThread: 0x100703550>{number = 1, name = main}
2019-04-16 17:10:51.809845+0800[25074:3958399] 线程:1>>><NSThread: 0x100629510>{number = 2, name = (null)}
2019-04-16 17:10:51.810150+0800[25074:3958399] 线程:2>>><NSThread: 0x100629510>{number = 2, name = (null)}
2019-04-16 17:10:51.810171+0800[25074:3958399] 线程:3>>><NSThread: 0x100629510>{number = 2, name = (null)}

可以看到,结果开辟了多个线程,在这个线程中并发执行,并且与旧线程并行执行。

3、死锁

GCD同步异步,并行和串行中,可以实现嵌套的使用,但是在嵌套使用时,不理解的人在随便乱用GCD嵌套使用会造成程序执行中断的严重问题,造成这个问题的原因就是死锁。之前提到过,队列是FIFO先进先出的形式,一个线程可能有多个队列,在同一个线程的同一个队列中,先进入队列的任务要先出去,后面的队列才能出去,如图所示:


image.png

接下来我们来用代码看看几种会出现问题的可能性和分析具体原因:

案例一
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"开始");
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"线程:1>>>%@",[NSThread currentThread]);
        });
        NSLog(@"线程:2>>>%@",[NSThread currentThread]);
    }
    return 0;
}

当在主线程执行完,发现执行崩溃,这是为什么呢?我们刚才说过队列是顺序执行,意味着一个队列还没结束,后面的队列就无法结束。当在主线程执行同步队列时,就表示主线程的主队列又创建了一个任务。线程1和线程2都在主线程中的同一个队列里,并且这个任务是顺序执行,表示线程1创建的同时需要立马就执行,但是线程2还没执行完毕,因此线程1没办法先进线程原则执行所以线程1需要等待线程2执行。但是线程2要执行完也需要经过线程1,因此线程2也要等待线程1执行完。这样互相等着造成线程无法释放,阻塞主线程导致死锁。

案例二
NSLog(@"开始");
dispatch_async(dispatch_get_main_queue(), ^{
      NSLog(@"线程:1>>>%@",[NSThread currentThread]);
});
NSLog(@"线程:2>>>%@",[NSThread currentThread]);
2019-04-16 17:44:36.446608+0800[25372:4016930] 开始
2019-04-16 17:44:36.447153+0800[25372:4016930] 线程:2>>><NSThread: 0x10050d420>{number = 1, name = main}
2019-04-16 17:44:36.447153+0800[25372:4016930] 线程:1>>><NSThread: 0x10050d420>{number = 1, name = main}

发现虽然都在主线程,因为是异步的,因此线程2不用等待线程1执行,因此不会造成死锁。

案例三

接下来我们在主线程中,加入一个同步的全局队列,我们来看看结果:

NSLog(@">>>>1>>>%@",[NSThread currentThread]);
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
    NSLog(@">>>>2>>>%@",[NSThread currentThread]);
});
NSLog(@">>>>3>>>%@",[NSThread currentThread]);
2019-04-18 10:00:31.070717+0800 block_test[43552:5006132] >>>>1>>><NSThread: 0x100705db0>{number = 1, name = main}
2019-04-18 10:00:31.071007+0800 block_test[43552:5006132] >>>>2>>><NSThread: 0x100705db0>{number = 1, name = main}
2019-04-18 10:00:31.071027+0800 block_test[43552:5006132] >>>>3>>><NSThread: 0x100705db0>{number = 1, name = main}

发现结果不会报错,正常按顺序打印,因为主线程的主队列和主线程的全局队列不在同一个队列中,因此不会出现死锁的现象。当执行dispatch_sync的时候,系统阻塞主队列,并且执行全局队列,当全局队列执行完,再回到主队列中继续执行。

案例四

为了验证串行和并行是否是死锁的一个条件,我们在来看两个例子,首先是在同一个线程中使用串行队列去执行。

NSLog(@">>>>1>>>%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
    NSLog(@">>>>2>>>%@",[NSThread currentThread]);
    dispatch_sync(queue, ^{
        NSLog(@">>>>3>>>%@",[NSThread currentThread]);
    });
});
NSLog(@">>>>4>>>%@",[NSThread currentThread]);

结果直接崩溃,说明了串行队列确实是会造成死锁。

NSLog(@">>>>1>>>%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
    NSLog(@">>>>2>>>%@",[NSThread currentThread]);
    dispatch_sync(queue, ^{
        NSLog(@">>>>3>>>%@",[NSThread currentThread]);
    });
});
NSLog(@">>>>4>>>%@",[NSThread currentThread]);
2019-04-18 10:07:51.532307+0800 block_test[43623:5018697] >>>>1>>><NSThread: 0x100605db0>{number = 1, name = main}
2019-04-18 10:07:51.532575+0800 block_test[43623:5018697] >>>>2>>><NSThread: 0x100605db0>{number = 1, name = main}
2019-04-18 10:07:51.532620+0800 block_test[43623:5018697] >>>>3>>><NSThread: 0x100605db0>{number = 1, name = main}
2019-04-18 10:07:51.532665+0800 block_test[43623:5018697] >>>>4>>><NSThread: 0x100605db0>{number = 1, name = main}

发现结果打印出来的是正常的,正好验证了我们的猜想。

因此满足死锁需要具备两个条件:

  • 在同一个线程中,出现多个同步任务,并且任务之间存在嵌套关系
  • 存在嵌套关系的任务,是在同一个队列中

4、dispatch_once

说到单例相信大家都不会陌生,在项目中,经常性的使用到单例,在应用程序的生命周期内仅执行一次块对象。网上有非常多的对于单例的使用这里就不提。
在使用单例中,可以通过dispatch_once或者dispatch_once_f的方式,两者的区别只是在于,前者是以block的方式去设置单例的方法;后者则是可以添加C函数的方式设置,dispatch _function _t类型。

+(Person *)sharedInstance
{
    static Person *sharedManager;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedManager = [[Person alloc] init];
    });
 //   dispatch_once_f(&onceToken, nil, test);
    return sharedManager;
}

void test(void *context) {
    int *c = context;
    NSLog(@"%d", *c);
}
dispatch_once也会造成死锁
+(TestA *)sharedInstance
{
    static TestA *sharedManager;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedManager = [[TestA alloc] init];
        TestB *test = [self sharedInstance2];
    });
    return sharedManager;
}

+(TestB *)sharedInstance2
{
    static TestB *sharedManager;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedManager = [[TestB alloc] init];
        TestA *test = [self sharedInstance];
    });
    
    return sharedManager;
}

死锁的原因是因为dispatch_once底层是用信号量去控制线程的释放,首先会先判断是否是第一个运行,如果是的情况,执行block内部的方法,并做一个死循环去监控是否有其他线程进入该block函数中,如果有就建立信号链表,用两个指针指向信号头尾,在死循环中,逐一的处理信号。但是发现两个方法互相调用过程,信号量是互相等待,造成死锁的情况。
顺便说下,当非第一个调用dispatch_once,则进入另一个死循环,如果block的请求还没有结束,则将后续的操作添加到链表中进行处理,处理完跳出死循环。

5、dispatch_apply

dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API,将单个块提交到调度队列,并使块执行指定的次数。该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等到全部的处理执行结束

void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));

dispatch_apply是并行的调度方法,用户使用该方法,可以进行无序多次循环的操作,并且会阻断主线程的运行等待所有循环都操作完之后才会继续接下去的主线程操作:

NSLog(@"开始");
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_apply(10, queue, ^(size_t size) {
    NSLog(@">>>>  %zu  >>>%@",size,[NSThread currentThread]);
});
NSLog(@"结束");
2019-04-24 09:32:23.261395+0800 block_test[13160:426551] 开始
2019-04-24 09:32:23.262115+0800 block_test[13160:426551] >>>>  0  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:32:23.262180+0800 block_test[13160:426551] >>>>  4  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:32:23.262215+0800 block_test[13160:426551] >>>>  5  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:32:23.262226+0800 block_test[13160:426587] >>>>  1  >>><NSThread: 0x102807010>{number = 2, name = (null)}
2019-04-24 09:32:23.262232+0800 block_test[13160:426551] >>>>  6  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:32:23.262232+0800 block_test[13160:426595] >>>>  3  >>><NSThread: 0x10070a6b0>{number = 3, name = (null)}
2019-04-24 09:32:23.262251+0800 block_test[13160:426551] >>>>  7  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:32:23.262258+0800 block_test[13160:426587] >>>>  8  >>><NSThread: 0x102807010>{number = 2, name = (null)}
2019-04-24 09:32:23.262265+0800 block_test[13160:426595] >>>>  9  >>><NSThread: 0x10070a6b0>{number = 3, name = (null)}
2019-04-24 09:32:23.262275+0800 block_test[13160:426590] >>>>  2  >>><NSThread: 0x10070aa90>{number = 4, name = (null)}
2019-04-24 09:32:23.282560+0800 block_test[13160:426551] 结束

从打印的结果上,可以看出dispatch_apply是在多线程上操作,并且会再新线程全部结束后,再做主线程刷新操作。
在官方文档的,提供了DISPATCH_APPLY_AUTO,并建议使用此参数作为queue,因为这会导致任务在其服务质量类(QOS)最适合当前执行上下文的队列上运行。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        
        dispatch_apply(10, queue, ^(size_t index) {
            NSLog(@">>>>  %zu  >>>%@",index,[NSThread currentThread]);
        });
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"结束");
        });
    });

当然如果队列是串行的方法也是等到串行结束后再执行

NSLog(@"开始");
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_SERIAL);
dispatch_apply(10, queue, ^(size_t index) {
    NSLog(@">>>>  %zu  >>>%@",index,[NSThread currentThread]);
});
NSLog(@"结束");
2019-04-24 09:54:06.183587+0800 block_test[13419:450661] 开始
2019-04-24 09:54:06.184322+0800 block_test[13419:450661] >>>>  0  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184432+0800 block_test[13419:450661] >>>>  1  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184464+0800 block_test[13419:450661] >>>>  2  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184496+0800 block_test[13419:450661] >>>>  3  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184511+0800 block_test[13419:450661] >>>>  4  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184550+0800 block_test[13419:450661] >>>>  5  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184593+0800 block_test[13419:450661] >>>>  6  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184607+0800 block_test[13419:450661] >>>>  7  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184622+0800 block_test[13419:450661] >>>>  8  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184636+0800 block_test[13419:450661] >>>>  9  >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.188585+0800 block_test[13419:450661] 结束

6、dispatch_queue_get_label 和 dispatch_set_target_queue 和 dispatch_queue_set_specific

dispatch_queue_get_label 是用来获取队列标签,并且系统也提供一个关键词DISPATCH_CURRENT_QUEUE_LABEL用来获取当前队列的标签,使用如下:

dispatch_queue_t queue = dispatch_queue_create("this is a queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
    NSLog(@">> 1 >> %s", dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
});
NSLog(@">> 2 >> %s", dispatch_queue_get_label(queue));
NSLog(@">> 3 >> %s", dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));

dispatch_set_target_queue 这个参数,将给一个队列设置目标队列,object是要修改的对象(不能指定主队列和全局队列),queue是目标对象队列。如果希望系统提供适合当前对象的队列,请指定DISPATCH_TARGET_QUEUE_DEFAULT

void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);

那么设置成目标对象的队列即object参数队列会变成什么样呢?
对于一个已经存在的队列去改变队列的优先级和qos的设定,使其与目标队列一致。

dispatch_queue_t targetQueue = dispatch_queue_create("targetQueue", DISPATCH_QUEUE_SERIAL);//目标队列
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);//串行队列
    dispatch_queue_t queue2 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并发队列
    //设置参考
    dispatch_set_target_queue(queue1, targetQueue);
    dispatch_set_target_queue(queue2, targetQueue);
    
    dispatch_async(queue2, ^{
        NSLog(@"job3 in %@",[NSThread currentThread]);
    });
    dispatch_async(queue2, ^{
        NSLog(@"job2 in %@",[NSThread currentThread]);
    });
    dispatch_async(queue1, ^{
        NSLog(@"job1 in %@",[NSThread currentThread]);
    });

打印出来的结果是串行同步输出:

2019-04-24 10:54:32.406594+0800 test_1[1514:2485209] job3 in <NSThread: 0x1c0660600>{number = 3, name = (null)}
2019-04-24 10:54:32.406735+0800 test_1[1514:2485209] job2 in <NSThread: 0x1c0660600>{number = 3, name = (null)}
2019-04-24 10:54:32.407308+0800 test_1[1514:2485209] job1 in <NSThread: 0x1c0660600>{number = 3, name = (null)}

设置目标队列时,请勿在队列层次结构中创建周期。换句话说,不要将队列A的目标设置为队列B,并将队列B的目标设置为队列A.例如:

// 以下是错误的使用方式
dispatch_set_target_queue(queue1, targetQueue);
    dispatch_set_target_queue(targetQueue, queue1);

dispatch_queue_set_specific 给当前的队列设定一个标识,使用方法:

static void *queueKey1 = "queueKey1";
dispatch_queue_t queue1 = dispatch_queue_create(queueKey1, DISPATCH_QUEUE_SERIAL);
dispatch_queue_set_specific(queue1, queueKey1, &queueKey1, NULL);

NSLog(@"当前队列是: %@ 。",dispatch_get_current_queue());
dispatch_sync(queue1, ^{
    [NSThread sleepForTimeInterval:1];
    
    if (dispatch_get_specific(queueKey1)) {
        //当前队列是queue1队列,所以能取到queueKey1对应的值,故而执行
        NSLog(@"当前队列是: %@ 。",dispatch_get_current_queue());
    }else{
        NSLog(@"当前队列是: %@ 。",dispatch_get_current_queue());
    }
});

6、DispatchWorkItem(dispatch_block_t)派遣工作调度块

GCD允许对调度回调block工作进行,以附加或执行依赖项的方式封装触发(dispatch_block_t)。调度工作项封装要在调度队列queue或调度组group内执行的工作。您还可以将工作项用作调度源事件,注册或取消处理程序。

dispatch_block_t dispatch_block_create(dispatch_block_flags_t flags, dispatch_block_t block);
//使用现有块和给定标志在堆上创建新的调度块。

dispatch_block_t dispatch_block_create_with_qos_class(dispatch_block_flags_t flags, dispatch_qos_class_t qos_class, int relative_priority, dispatch_block_t block);
//从现有块和给定标志创建新的调度块,并为其分配指定的服务质量类和相对优先级。

dispatch_block_t
//提交给调度队列的块的原型,它不带参数且没有返回值。

dispatch_block_flags_t
DISPATCH_ENUM(dispatch_block_flags, unsigned long,
    DISPATCH_BLOCK_BARRIER = 0x1,
    DISPATCH_BLOCK_DETACHED = 0x2,
    DISPATCH_BLOCK_ASSIGN_CURRENT = 0x4,
    DISPATCH_BLOCK_NO_QOS_CLASS = 0x8,
    DISPATCH_BLOCK_INHERIT_QOS_CLASS = 0x10,
    DISPATCH_BLOCK_ENFORCE_QOS_CLASS = 0x20,
);
// 创建派遣标志,来区分派遣调度block的设置处理事务

dispatch_block_perform
//从指定的块和标志创建,同步执行和释放调度块。

添加完成处理程序
dispatch_block_notify
//在完成指定的调度块的执行时,计划要提交到队列的通知块。

延迟执行工作项
dispatch_block_wait
//同步等待,直到指定的调度块的执行完成或者直到指定的超时已经过去。

取消工作项
dispatch_block_cancel
//异步取消指定的调度块。

dispatch_block_testcancel
//测试是否已取消给定的调度块。

使用的方式,创建dispatch_block_t,并在队列执行中,添加block:

dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并发队列
dispatch_block_t block = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
    NSLog(@">>>>>1");
    sleep(2);
    NSLog(@">>>>>>2");
});
dispatch_async(queue, block);

当使用dispatch_block_cancel,如果block还未执行时允许取消block执行的方法,正在执行的block不能取消。例如下,最后没有打印数据因为,已经取消执行block的方法:

dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并发队列
dispatch_block_t block = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
    NSLog(@">>>>>1");
    sleep(2);
    NSLog(@">>>>>>2");
});
dispatch_async(queue, block);
dispatch_block_cancel(block);

dispatch_block_wait是阻塞当前线程去执行block的操作,其规则是计时器的方式,设定计时器dispatch_time_t,在计时器结束之前,如果block执行完毕,则返回0,或者计时器计算后执行当前线程返回1,可用来设置在指定时间内是否请求超时:

dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并发队列
dispatch_block_t block = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
    NSLog(@">>>>>1");
    sleep(2);
    NSLog(@">>>>>>2");
});
dispatch_async(queue, block);

dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
long result = dispatch_block_wait(block, timeout);
if (result == 0) {
    NSLog(@"执行成功");
} else {
    NSLog(@"执行超时");
}

举一个例子,在一个dispatch_block_t对象,并且设置2秒超时,并在超时中做取消block的操作,看看结果:

dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并发队列
dispatch_block_t block = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
    NSLog(@">>>>>1");
    sleep(5);
    NSLog(@">>>>>>2");
});
dispatch_async(queue, block);

dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
long resutl = dispatch_block_wait(block, timeout);
if (resutl == 0) {
    NSLog(@"执行成功");
} else {
    NSLog(@"执行超时");
    dispatch_block_cancel(block);
}
2019-04-24 14:29:18.940770+0800 test_1[1603:2562465] >>>>>1
2019-04-24 14:29:20.941842+0800 test_1[1603:2562400] 执行超时
2019-04-24 14:29:23.946158+0800 test_1[1603:2562465] >>>>>>2
2019-04-24 14:29:23.946454+0800 test_1[1603:2562458] 结束

打印的结果是请求超时了,因为block内部执行了5秒,dispatch_block_cancel在执行过程中也没办法关闭block正好验证我们的答案。

dispatch_block_notify

是在block执行结束后会调用该函数进行后面的处理,即使调用了dispatch_block_cancel,也会进入该方法中,通常用来阻塞当前线程,等待block操作完,DISPATCH_TIME_NOW,DISPATCH_TIME_FOREVER表示计时器无限时间,即永远阻塞必须等到group执行完才能执行。。

dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并发队列
dispatch_block_t block = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
    NSLog(@">>>>>1");
    sleep(5);
    NSLog(@">>>>>>2");
});
dispatch_async(queue, block);

dispatch_block_notify(block, queue, ^{
    NSLog(@"结束");
});

7、Dispatch Group(调度组)

创建调度组
dispatch_group_create
//创建一个可以为其分配块对象的新组。

dispatch_group_t
//提交到队列以进行异步调用的一组块对象。

OS_dispatch_group
将工作添加到组中
dispatch_group_async
//异步调度块以执行,同时将其与指定的调度组关联。

dispatch_group_async_f
//将应用程序定义的函数提交给调度队列,并将其与指定的调度组关联。

添加完成处理程序
dispatch_group_notify
//当一组先前提交的块对象完成时,计划将块对象提交到队列。

dispatch_group_notify_f
//计划在一组先前提交的块对象完成时将应用程序定义的函数提交到队列。

等待任务完成执行
dispatch_group_wait
//同步等待先前提交的块对象完成; 如果块在指定的超时时间段之前未完成,则返回。

手动更新组
dispatch_group_enter
//显式指示块已进入组。

dispatch_group_leave
//显式指示组中的块已完成执行。

在平时简单的使用过程中,前面的一些方式基本可以完成,但是在针对复杂的请求,为了能在多线程中体现和更好的去操作,GCD提供的组队列的方式,对用户的逻辑操作使用dispatch_group_t,可以解决一些问题。

dispatch_group允许您合并一组任务并同步group上的行为。将多个块附加到dispatch_group_t并将它们安排在同一队列或不同队列上进行异步执行。当所有块完成执行后,该组将执行其完成处理程序。您也可以同步等待组中的所有块完成执行。

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并发队列
for (int index = 0; index < 10; index ++) {
        dispatch_group_async(group, queue, ^{
            // 里面不能使用异步,除非与dispatch_group_enter一起用
            NSLog(@"开始请求 >> %d",index);
        });
}
dispatch_group_notify(group, queue, ^{
    NSLog(@"———————————— 打印结果 ——————————————");
});

第二种dispatch_group_enter 和dispatch_group_leave的方式去控制group是否全部执行完。

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并发队列
    for (int index = 0; index < 10; index ++) {
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            NSLog(@"%d",index);
            sleep(2);
            NSLog(@"%d >> 结束",index);
            dispatch_group_leave(group);
        });
    }
dispatch_group_notify(group, queue, ^{
    NSLog(@"———————————— 打印结果 ——————————————");
});

dispatch_group_notify是在执行完之后的通知,为了能执行完毕,dispatch_group_enter 和dispatch_group_leave必须成对出现否则会崩溃。
dispatch_group_wait 同样和dispatch_block_wait一样,是用来阻塞当前线程的,只有当计时器结束(返回1),或者group执行完毕(返回0)才会继续执行线程后的操作,DISPATCH_TIME_NOW,DISPATCH_TIME_FOREVER表示计时器无限时间,即永远阻塞必须等到group执行完才能执行。

8、Dispatch Semaphore(信号量)和 Dispatch Barrier

信号量是什么,信号量是为了控制等待功能可以提前释放而存在的,用来对线程的控制计数器。dispatch_semaphore_wait(等待,信号量递减) 或 dispatch_semaphore_signal (发送,信号量递增)阻塞线程的同时,可以在用户认为操作完后可以将阻塞线程手动释放而存在的。
调度信号量是传统计数信号量的有效实现。仅当需要阻止调用线程时,Dispatch信号量才会调用内核。如果调用信号量不需要阻塞,则不进行内核调用。

    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);

dispatch_semaphore_signal(sem);
dispatch_async(queue, ^{
    NSLog(@"111");
    sleep(2);
    dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

dispatch_async(queue, ^{
    NSLog(@"222");
    sleep(2);
    dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

dispatch_async(queue, ^{
    NSLog(@"333");
    sleep(2);
});
2019-04-24 17:35:29.780870+0800 test_1[1667:2627449] 111
2019-04-24 17:35:31.786097+0800 test_1[1667:2627449] 222
2019-04-24 17:35:33.800574+0800 test_1[1667:2627450] 333

根据打印结果可以知道,当执行并行异步的时候,原本是同步进行的线程,因为执行了dispatch_semaphore_wait阻塞了线程后面的执行,又使用dispatch_semaphore_signal去回复线程执行,将原本并行的线程,通过这种方式变成串行的线程。
对信号量的使用在复杂的需求上经常性的会出现,后面我会根据一些实战让大家更了解。

Dispatch Barrier

使用屏障同步调度队列中一个或多个任务的执行。向并发调度队列添加屏障时,队列会延迟屏障块(以及屏障后提交的任何任务)的执行,直到所有先前提交的任务完成执行。在前面的任务完成执行之后,队列自己执行屏障块。一旦屏障块完成,队列将恢复其正常执行行为。

dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);
for (int i=0; i<3; i++) {
    dispatch_async(queue, ^{
        NSLog(@"111 >>> %@", [NSThread currentThread]);
    });
}
dispatch_barrier_async(queue, ^{
    NSLog(@"barrier结束了");
});
dispatch_async(queue, ^{
    NSLog(@"333 >>> %@", [NSThread currentThread]);
});    
2019-04-24 17:57:28.864959+0800 test_1[1696:2637202] 111 >>> <NSThread: 0x1c4079a00>{number = 4, name = (null)}
2019-04-24 17:57:28.864959+0800 test_1[1696:2637201] 111 >>> <NSThread: 0x1c4079880>{number = 3, name = (null)}
2019-04-24 17:57:28.865091+0800 test_1[1696:2637202] 111 >>> <NSThread: 0x1c4079a00>{number = 4, name = (null)}
2019-04-24 17:57:28.865637+0800 test_1[1696:2637199] barrier结束了
2019-04-24 17:57:28.865707+0800 test_1[1696:2637199] 333 >>> <NSThread: 0x1c04682c0>{number = 5, name = (null)}

从打印的结果可以看出,虽然是异步并行的队列,但是前面的循环在没执行完之前,后面的都不会执行,就是因为dispatch_barrier_async起到了作用,会阻隔后面的操作。

dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_SERIAL);
for (int i=0; i<3; i++) {
    dispatch_async(queue, ^{
        NSLog(@"111 >>> %@", [NSThread currentThread]);
    });
}
dispatch_barrier_sync(queue, ^{
    NSLog(@"barrier结束了");
});
dispatch_sync(queue, ^{
    NSLog(@"333 >>> %@", [NSThread currentThread]);
});
2019-04-24 18:05:36.530328+0800 test_1[1712:2641128] 111 >>> <NSThread: 0x1c0279300>{number = 3, name = (null)}
2019-04-24 18:05:36.530422+0800 test_1[1712:2641128] 111 >>> <NSThread: 0x1c0279300>{number = 3, name = (null)}
2019-04-24 18:05:36.530460+0800 test_1[1712:2641128] 111 >>> <NSThread: 0x1c0279300>{number = 3, name = (null)}
2019-04-24 18:05:36.530498+0800 test_1[1712:2641110] barrier结束了
2019-04-24 18:05:36.530642+0800 test_1[1712:2641110] 333 >>> <NSThread: 0x1c006a300>{number = 1, name = main}

即使是同步串行的barrier 依然会阻塞当前线程和后面的队列,等待前面队列结束。

结束语

到此GCD的常用的对象和方法都介绍完了,养兵千日用兵一时,自己根据上面的理解带入到开发中。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容