GCD

1.队列

最大的分类有两种:串行和并行,对应的创建方法如下

// 串行
dispatch_queue_t synQueue = dispatch_queue_create("syn", DISPATCH_QUEUE_SERIAL);
// 并行
dispatch_queue_t asyncQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT);

上面的这种创建方式是自定义队列,第一个参数是队列标识,第二个参数是指定是哪种队列。

系统有本身的队列,比如主队列和全局队列

// 主队列
dispatch_get_main_queue();
// 全局队列
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

主队列是串行队列,这也是为什么我们网络请求一般不在主队列发起,因为这样在请求返回前会阻塞当前队列,无法进行其他操作;全局队列是并行队列,第一个参数是优先级,第二个参数是给苹果预留的,一般为0或NULL。

所以一般面试问GCD有几种队列,一般是主队列、全局队列和自定义队列。


2.执行方法

串行执行方法是

dispatch_sync(syncQueue, ^{});

并行执行方法是

dispatch_async(asyncQueue, ^{});

上面的写法是,串行队列调用串行方法,并行队列调用并行方法,那如果交叉过来,串行队列调用并行方法,并行队列调用串行方法,执行顺序会是如何呢?如果一个队列先后调用串行和并行方法,又是怎样执行的?

测试代码和过程我就不写了,这里先写自己测试后的结论:

串行队列执行并行方法,下一个任务会等上一个任务完全完成后才开始执行;并行队列执行串行方法,是按顺序执行。


3.barrier(屏障、栅栏)

并行队列并行执行的时候,所有任务都是随时执行的,但如果其中一个任务很重要,需求中它将会影响后面队列中的任务,该如何实现?GCD中有一个功能,这个功能类似于创建一个屏障、栅栏区域来隔开前后的任务,前面的任务执行完后,再执行栅栏区域的任务,才能执行后面的任务。

    dispatch_queue_t asyncQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(asyncQueue, ^{
        NSLog(@"1");
    });
    
    dispatch_async(asyncQueue, ^{
        NSLog(@"2");
    });
    
    dispatch_barrier_async(asyncQueue, ^{
        NSLog(@"barrier");
    });
    
    dispatch_async(asyncQueue, ^{
        NSLog(@"3");
    });
    
    dispatch_async(asyncQueue, ^{
        NSLog(@"4");
    });

输出结果是1,2,barrier,3,4,也有可能会是2,1,barrier,4,3,但barrier一定会在前两个任务执行完之后,后两个任务执行之前执行的。


4.GCD和线程同步

如果多个线程访问同一处代码,那么可能会出现问题,比如对同一个值的get和set方法,多线程的时候,由于执行的时机是随时的,所以我们有可能访问get方法获取到的不是最新的值。通常是使用锁来实现同步机制,比较常用是@synchronized或者NSLock及其子类来加锁。

- (void)lockMethod {
    @synchronized(self) {
        //do something
    }
}

以上这种写法是根据给的对象,自动创建一个锁,等到block总的代码执行完,就释放了锁。但是以上的这个例子代码里,由于锁的对象是self,@synchronized的作用是保证此时没有其他线程对self对象进行访问,这样如果我们在访问加锁程序的同时,就不能访问其他无关的代码了,所以滥用@synchronized会降低代码效率。

NSLock及其子类与@synchronized有一种缺陷,在极其极端的情况下,同步块会导致死锁,另外,效率也不见得很高。

替代方案是使用GCD,示例代码如下:

dispatch_queue_t asyncQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT);

- (NSString *)stringA {
    __block NSString* A;
    dispatch_async(asyncQueue, ^{
        A = _localA;
    });
    return A;
}

- (void)setStringA:(NSString *)A {
    dispatch_barrier_async(asyncQueue, ^{
        _localA = A;
    });
}

上面的示例里,所有的读取A的操作,都被barrier屏蔽住,必须等赋值完后才能读取,此时读取到的A是最新的值。


5.任务组group

GCD可以把任务分组,调用者会在回调函数中收到一组任务结束的通知。

任务组的创建:

dispatch_group_t group = dispatch_group_create();

通常的调用方法:

dispatch_queue_t asyncQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT);

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, asyncQueue, ^{
    NSLog(@"1");
});

dispatch_group_async(group, asyncQueue, ^{
    NSLog(@"2");
});

dispatch_group_async(group, asyncQueue, ^{
    NSLog(@"3");
});

dispatch_group_notify(group, asyncQueue, ^{
    NSLog(@"end");
});

等效于下面的这种方式,但区别是在任务完成前会阻塞:

dispatch_queue_t asyncQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT);

dispatch_group_t group = dispatch_group_create();

dispatch_group_enter(group);

dispatch_async(asyncQueue, ^{
    NSLog(@"1");
    dispatch_group_leave(group);
});

dispatch_group_enter(group);

dispatch_async(asyncQueue, ^{
    NSLog(@"2");
    
    dispatch_group_leave(group);
});

dispatch_group_enter(group);

dispatch_async(asyncQueue, ^{
    NSLog(@"3");
    
    dispatch_group_leave(group);
});

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

NSLog(@"end");

dispatch_group_enter(group)和dispatch_group_leave(group)必须成对出现,表示一个任务进入组、离开组,类似于引用计数的retain和release。dispatch_group_wait是用来阻塞线程的,第二个参数是时间参数,表示要阻塞多久,我们用DISPATCH_TIME_FOREVER表明一直等,等到任务组都执行完才能向下执行其他的。如果在调用enter之后,没有对应的leave,那么这一任务永远执行不完,会由dispatch_group_wait一直阻塞着。

思考:如果是一个任务组,执行两个并行队列的所有任务,会是如何?


6.循环执行

如果我们碰上需要循环执行某些任务,比如遍历一个数组做操作,又不想阻塞线程,该如何做呢?

用for循环这么做:

dispatch_queue_t asyncQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT);

for (id obj in array) {
    dispatch_async(asyncQueue, ^{
        // do something
    });
}

但如果再加个需求,在遍历操作执行完后,才执行下一个任务,显然上面的这个方法不合适,用串行又显得不够好。现在我们开业参考下上面的group来实现:

dispatch_queue_t asyncQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();

for (id obj in array) {
    
    dispatch_group_async(group, asyncQueue, ^{
        // do something
    });
    
    dispatch_group_notify(group, asyncQueue, ^{
       // after end do something
    });
}

幸好,对于循环,GCD提供了一个dispatch_apply函数来实现:

dispatch_queue_t asyncQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT);

dispatch_apply(10, asyncQueue, ^(size_t index) {
    NSLog(@"index %ld", index);
});
NSLog(@"end");

但是dispatch_apply是同步方法,会阻塞线程,于是对上面的代码做一下调整:

dispatch_queue_t asyncQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(asyncQueue, ^(){
    dispatch_apply(10, asyncQueue, ^(size_t index){
        NSLog(@"index %ld", index);
    });
    NSLog(@"end");
});

7.单例

+ (id)shareInstance {
    static MyClass *myShareInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        myShareInstance = [[MyClass alloc] init];
    });
    return myShareInstance;
}

8.延时delay

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (5.0 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
    // do something
});

示例代码是延迟5秒。GCD的延迟方法比起performSelector的延迟方法好处是,代码集中不分散,不用另外在写执行方法,而且传参数没有限制,performSelector的参数必须是id类型。


9.优先级

调用全局队列的时候,有个参数是优先级,但一般是默认优先级DISPATCH_QUEUE_PRIORITY_DEFAULT,一共有四个优先级,按顺序是:高、默认、低、后台。高优先级会先执行,但注意,在极端的情况下会出现优先级反转的情况,低优先级的任务占有资源导致高优先级任务无法执行。


10.信号量

对于多个线程访问同个资源,GCD还提供是一种解决方法,就是信号量dispatch_semaphore

dispatch_queue_t asynQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT);
 
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
for (int i = 0; i < 20; i++)
{
    dispatch_async(asynQueue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"semaphore %@------ %i", semaphore, i);
        sleep(2);
        dispatch_semaphore_signal(semaphore);
    });
}

dispatch_semaphore_create(2)创建了一个总量为2的信号量;

dispatch_semaphore_wait是等待信号,并让信号量-1,如果获取到的信号量是0,那么根据设置的超时时间进行等待,例子里设置的超时时间是一直;

dispatch_semaphore_signal是发送信号,并让信号量+1;

这套信号机制是不是很类似引用计数。所以,上面的这段代码并发了20个任务,每个任务都会有sleep,但每执行2个任务,经由dispatch_semaphore_wait减了两次,就为0,其他的任务只能等sleep后dispatch_semaphore_signal加回信号量才能执行,如此反复。


最后一些注意

执行异步方法时,是需要拷贝block的,所以若拷贝所需要的时间超过执行的时间,显得效率降低,异步的效果得不偿失。

不要调用dispatch_get_current_queue(),因为如果当前队列正在执行同步方法,会引起死锁。所幸这个方法苹果已经废弃了。

你可能感兴趣的:(GCD)