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, ^{});

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

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

 并行队列并行执行,随意输出,不阻塞主线程

 串行队列串行执行,顺序输出,阻塞主线程

 并行队列串行执行,顺序输出,阻塞主线程

 串行队列并行执行,顺序输出,不阻塞主线程

Tip:串行和并行就像是单车道和多车道,单车道再运行多辆行驶,也只能每次通过一辆,多车道可以多辆行驶,也可以排队行驶。

 

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一直阻塞着。

但这并不是说dispatch_group_enter(group)和dispatch_group_leave(group)写法太繁琐,没啥用。比如下面这段代码

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_async(asyncQueue, ^{
        sleep(3);
        NSLog(@"1-1");
    });
});
dispatch_group_async(group, asyncQueue, ^{
    NSLog(@"2");
});
dispatch_group_async(group, asyncQueue, ^{
    NSLog(@"3");
});

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

NSLog(@"end");

结果:

2016-12-01 22:52:09.189  3
2016-12-01 22:52:09.189  2
2016-12-01 22:52:09.189  1
2016-12-01 22:52:09.189  end
2016-12-01 22:52:09.189  notify
2016-12-01 22:52:12.263  1-1

从结果可以看到,group没有处理任务1里面的异步子任务,因为queue中的block是执行到末尾就返回的。如果要同步到其中的子任务,需要这样改写

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_async(asyncQueue, ^{
        sleep(3);
        NSLog(@"1-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");

结果:
2016-12-01 23:00:12.279  2
2016-12-01 23:00:12.279  3
2016-12-01 23:00:12.279  1
2016-12-01 23:00:15.350  1-1
2016-12-01 23:00:15.351  end

关键是把dispatch_group_enter和dispatch_group_leave放到合适的地方去。

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

 

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");

结果:
index 1
index 0
index 3
index 2
index 4
index 5
index 6
index 7
index 8
index 9
end

但是dispatch_apply是同步方法,会阻塞线程,所以不要在主线程调用这个方法,而且index不是按顺序输出调用的。于是是对上面的代码做一下调整:

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加回信号量才能执行,如此反复。

可以用来坐线程同步,比如主线程与新线程同步(场景像请求动态域名)

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_queue_t asynQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT);

dispatch_sync(asynQueue, ^{
    NSLog(@"asyn queue task");
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"asyn queue task finish and wait");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), asynQueue, ^{
        dispatch_semaphore_signal(semaphore);
    });
});

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(semaphore);
NSLog(@"main queue task");

结果
2016-11-30 23:44:59.044  asyn queue task
2016-11-30 23:44:59.044  asyn queue task finish and wait
2016-11-30 23:45:02.045  main queue task

note:上面用的是串行执行,如果是并行执行的话,必须先sleep主线程保证新线程的优先执行。

 

11. dispatch_set_target_queue

dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t _Nullable queue);

dispatch_set_target_queue有两个作用,一个是继承target_queue的优先级,当然,系统的queue的优先级是无法用这个方法做修改的;第二个是可以修改层级关系,比如下面这段代码

dispatch_queue_t targetQueue = dispatch_queue_create("myTargetQueue", DISPATCH_QUEUE_SERIAL);

dispatch_queue_t queueA = dispatch_queue_create("myQueueA", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queueB = dispatch_queue_create("myQueueB", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queueC = dispatch_queue_create("myQueueC", DISPATCH_QUEUE_SERIAL);

dispatch_set_target_queue(queueA, targetQueue);
dispatch_set_target_queue(queueB, targetQueue);
dispatch_set_target_queue(queueC, targetQueue);

dispatch_async(queueA, ^{
    NSLog(@"A-1");
});
dispatch_async(queueB, ^{
    NSLog(@"B-1");
});
dispatch_async(queueC, ^{
    NSLog(@"C-1");
});
dispatch_async(queueB, ^{
    NSLog(@"B-2");
});
dispatch_async(queueA, ^{
    NSLog(@"A-2");
});
dispatch_async(queueC, ^{
    NSLog(@"C-2");
});

结果:
2016-12-01 22:14:15.247  A-1
2016-12-01 22:14:15.248  A-2
2016-12-01 22:14:15.248  B-1
2016-12-01 22:14:15.248  B-2
2016-12-01 22:14:15.249  C-1
2016-12-01 22:14:15.249  C-2

本来每个串行队列的调用都是异步的,但经过dispatch_set_target_queue指定到目标的串行队列后,执行顺序就有了先后,每个queue会先执行完其中所有任务,之后下一个queue才能执行,相当于按照queue的调用顺序,把这些queue排了个序。

 

12. dispatch_suspend和dispatch_resume

dispatch_queue_t asyncQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(asyncQueue, ^{
    NSLog(@"1");
    sleep(3);
    dispatch_resume(asyncQueue);
});
dispatch_suspend(asyncQueue);
dispatch_async(asyncQueue, ^{
    NSLog(@"2");
});
结果:
2016-12-01 23:29:41.170  1
2016-12-01 23:29:44.245  2

dispatch_suspend和dispatch_resume也是成对出现,否则会出现崩溃。

一段很神奇的代码

dispatch_queue_t myQueue = dispatch_queue_create("123456", DISPATCH_QUEUE_SERIAL);
dispatch_async(myQueue, ^{
    NSLog(@"1");
    sleep(3);
    dispatch_resume(myQueue);
});
NSLog(@"1.5");// 最关键的一句,如果不加这个,1和2是无法输出的
dispatch_suspend(myQueue);
dispatch_async(myQueue, ^{
    NSLog(@"2");
});
NSLog(@"3");

个人猜测,加入queue的block任务,不会立马执行,会在稍后判断是否有任务的加入,或者其他的针对queue操作,如果没有,才执行block任务。所以上面的1.5的作用是使得判断没有针对queue的操作,所以执行block的任务。注释掉1.5,会使得block还没被执行,就被挂起。

 

13.dispatch_source_t timer

dispatch_source_t timer可以做为定时器来使用

@property (nonatomic, strong) dispatch_source_t timer;

dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
self.timer = timer;
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
    NSLog(@"haha");
});
dispatch_resume(timer);
dispatch_source_set_timer(dispatch_source_t source, dispatch_time_t start, uint64_t interval, uint64_t leeway);

dispatch_source_set_timer的第三个参数是指定间隔时间,第四个参数是指定可以有多大误差。

如果是指定时间启动,那么需要设置第二个参数dispatch_time_t start

dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 1.0*NSEC_PER_SEC);

我们设置了执行的定时任务,也可以取消,我们把上面的代码改写一下

@property (nonatomic, strong) dispatch_source_t timer;

__block int i = 0;
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
self.timer = timer;
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
    NSLog(@"haha");
    i++;
    if (i == 3) {
        dispatch_source_cancel(timer);
    }
    
});
dispatch_resume(timer);
    
dispatch_source_set_cancel_handler(timer, ^{
    NSLog(@"cancel");
});

结果:
2016-12-04 15:27:41.080  haha
2016-12-04 15:27:44.144  haha
2016-12-04 15:27:47.121  haha
2016-12-04 15:27:47.121  cancel

NSTimer是我们常用的定时器的类,但是它有两个问题,一个是会持有target和userinfo,一个是计时不是太精准,因为它是跟着runloop走的。MSWeakTimer是一个处理定时器timer循环引用的,它就是使用了dispatch_source_t timer。

 

最后一些注意

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

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

在非ARC项目中,dispatch_queue_create生成的线程需要手动dispatch_release,即手动管理dispatch_queue_t内存。

线程直到执行完所有提交到其中处理的block执行完,才会释放,在其中任何地方调用dispatch_release,都不会立即被释放。

If your app isn’t using ARC, you should call dispatch_release on a dispatch queue when it’s no longer needed. 
Any pending blocks submitted to a queue hold a reference to that queue, so the queue is not deallocated until all pending blocks have completed.

 

转载于:https://my.oschina.net/u/574245/blog/524347

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值