转载

GCD那些事

GCD属于系统级的线程管理,在Dispatch queue中执行需要执行的任务性能非常的高。GCD这块已经开源,地址: http://libdispatch.macosforge.org 。GCD中的FIFO队列称为dispatch queue,用来保证先进来的任务先得到执行。这里我们从作用和场景同时分析,看一下到底在哪些地方会使用。单单只介绍作用的话,可能

二. 常用的GCD

1. dispatch_semaphore_t,dispatch_semaphore_wait,dispatch_semaphore_signal

作用:

在多线程下控制多线程的并发数目。

  1. 创建信号量,可以设置信号量的资源数。0表示没有资源,调用 dispatch_semaphore_wait 会立即等待。
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0) ;
  2. 等待信号,可以设置超时参数。该函数返回0表示得到通知,非0表示超时。 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) ;
  3. 通知信号,如果等待线程被唤醒则返回非0,否则返回0。
    dispatch_semaphore_signal(semaphore);

使用场景一:并发队列

比如现在我每次想执行10个任务。休息两秒。继续执行10个任务。可以这么写.

-(void) testSemaphore{
dispatch_group_t group = dispatch_group_create();
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10); //信号总量是10
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 100; i++)
{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//信号量-1
dispatch_group_async(group, queue, ^{
NSLog(@"%i",i);
sleep(2);
dispatch_semaphore_signal(semaphore); //信号量+1
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
}
```
执行结果:

```objc
2016-06-01 20:30:35.113 XXGCD[39559:306278] 7
2016-06-01 20:30:35.113 XXGCD[39559:306277] 6
2016-06-01 20:30:35.113 XXGCD[39559:306271] 0
2016-06-01 20:30:35.113 XXGCD[39559:306274] 3
2016-06-01 20:30:35.113 XXGCD[39559:306270] 1
2016-06-01 20:30:35.113 XXGCD[39559:306272] 2
2016-06-01 20:30:35.113 XXGCD[39559:306275] 4
2016-06-01 20:30:35.113 XXGCD[39559:306276] 5
2016-06-01 20:30:35.113 XXGCD[39559:306279] 8
2016-06-01 20:30:35.113 XXGCD[39559:306280] 9

2016-06-01 20:30:37.117 XXGCD[39559:306272] 11
2016-06-01 20:30:37.117 XXGCD[39559:306270] 12
2016-06-01 20:30:37.117 XXGCD[39559:306275] 10
2016-06-01 20:30:37.117 XXGCD[39559:306280] 13
2016-06-01 20:30:37.117 XXGCD[39559:306276] 15
2016-06-01 20:30:37.117 XXGCD[39559:306279] 14
2016-06-01 20:30:37.117 XXGCD[39559:306274] 16
2016-06-01 20:30:37.117 XXGCD[39559:306277] 17
2016-06-01 20:30:37.117 XXGCD[39559:306278] 18
2016-06-01 20:30:37.117 XXGCD[39559:306271] 19
...

使用场景二:异步队列中做事,等待回调后执行某件事

-(void) testAsynFinished{
__block BOOL isok = NO;

dispatch_semaphore_t sema = dispatch_semaphore_create(0);

XXEngine *engine = [[XXEngine alloc] init];
[engine queryCompletion:^(BOOL isOpen) {//sleep了3秒
isok = isOpen;
dispatch_semaphore_signal(sema);
NSLog(@"success!");
} onError:^(BOOL isOpen, int errorCode) {
isok = NO;
dispatch_semaphore_signal(sema);
NSLog(@"error!");

}];
NSLog(@"finished");
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}

执行结果:

2016-06-02 15:01:06.168 XXGCD[35443:290673] success!
2016-06-02 15:01:06.168 XXGCD[35443:290673] finished

使用场景三:生产者,消费者

-(void) testProductAndConsumer{

dispatch_semaphore_t sem = dispatch_semaphore_create(0);

dispatch_queue_t producerQueue = dispatch_queue_create("producer", DISPATCH_QUEUE_CONCURRENT);//生产者线程跑的队列
dispatch_queue_t consumerQueue = dispatch_queue_create("consumer", DISPATCH_QUEUE_CONCURRENT);//消费者线程跑的队列

__block int cakeNumber = 0;
dispatch_async(producerQueue, ^{ //生产者队列
while (1) {
if (!dispatch_semaphore_signal(sem))
{
NSLog(@"Product:生产出了第%d个蛋糕",++cakeNumber);
sleep(1); //wait for a while
continue;
}
}
});
dispatch_async(consumerQueue, ^{//消费者队列
while (1) {
if (dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, 0*NSEC_PER_SEC))){
if(cakeNumber > 0){
NSLog(@"Consumer:拿到了第%d个蛋糕",cakeNumber--);
}
continue;
}
}
});
}

执行结果:

2016-06-02 15:04:18.785 XXGCD[35593:294211] Product:生产出了第1个蛋糕
2016-06-02 15:04:18.785 XXGCD[35593:294209] Consumer:拿到了第1个蛋糕
2016-06-02 15:04:19.790 XXGCD[35593:294209] Consumer:拿到了第1个蛋糕
2016-06-02 15:04:19.790 XXGCD[35593:294211] Product:生产出了第1个蛋糕

2. dispatch_group_t,dispatch_group_notify

用来阻塞一个线程,直到一个或多个任务完成执行。有时候你必须等待任务完成的结果,然后才能继续后面的处理

使用场景一:并发队列

-(void) testGCDGroup{
dispatch_group_t group = dispatch_group_create();
for(int i = 0; i < 10; i++)
{
dispatch_group_enter(group);
[[XXEngine instance] doAsyncWorkWithCompletionBlock:^{
dispatch_group_leave(group);
}];
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"testGCDGroup1 更新UI操作");
}

等价于:

-(void) testGCDGroup2{
dispatch_group_t group = dispatch_group_create();
for(int i = 0; i < 10; i++)
{
dispatch_group_enter(group);
[[XXEngine instance] doAsyncWorkWithCompletionBlock:^{
dispatch_group_leave(group);
}];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"testGCDGroup2 更新UI操作");
});
}
```

执行结果:

```objc
2016-06-02 15:29:54.392 XXGCD[36197:313551] test
2016-06-02 15:29:54.392 XXGCD[36197:313569] test
2016-06-02 15:29:54.393 XXGCD[36197:313572] test
2016-06-02 15:29:54.393 XXGCD[36197:313571] test
2016-06-02 15:29:54.392 XXGCD[36197:313558] test
2016-06-02 15:29:54.392 XXGCD[36197:313568] test
2016-06-02 15:29:54.392 XXGCD[36197:313563] test
2016-06-02 15:29:54.393 XXGCD[36197:313570] test
2016-06-02 15:29:54.393 XXGCD[36197:313573] test
2016-06-02 15:29:54.393 XXGCD[36197:313574] test
2016-06-02 15:29:56.398 XXGCD[36197:313513] testGCDGroup1 更新UI操作

使用场景二:多任务执行完成,刷新UI

需要在主线程中执行,比如操作GUI,那么我们只要将main queue而非全局队列传给dispatch_group_notify函数就行了。

-(void) testGCDGroup3{
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, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"group1");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"group2");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"group3");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"testGCDGroup3 更新UI操作");
});
}

执行结果:

2016-06-02 15:58:29.041 XXGCD[36675:337871] group1
2016-06-02 15:58:30.041 XXGCD[36675:337880] group2
2016-06-02 15:58:31.043 XXGCD[36675:337884] group3
2016-06-02 15:58:31.043 XXGCD[36675:337834] testGCDGroup3 更新UI操作

3. dispatch_apply

是同步函数,会阻塞当前线程直到所有循环迭代执行完成。当提交到并发queue时,循环迭代的执行顺序是不确定的

static int i = 0;
-(void) testGCDApply{
dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, globalQ, ^(size_t index) {
// 执行10次
[self printTest];
});
}
-(void) printTest{
NSLog(@"%d",++i);
}

执行结果:

2016-06-02 16:06:50.799 XXGCD[37133:347348] 6
2016-06-02 16:06:50.799 XXGCD[37133:347329] 2
2016-06-02 16:06:50.799 XXGCD[37133:347346] 8
2016-06-02 16:06:50.799 XXGCD[37133:347336] 1
2016-06-02 16:06:50.799 XXGCD[37133:347340] 3
2016-06-02 16:06:50.799 XXGCD[37133:347296] 4
2016-06-02 16:06:50.799 XXGCD[37133:347347] 7
2016-06-02 16:06:50.799 XXGCD[37133:347349] 5
2016-06-02 16:06:50.799 XXGCD[37133:347329] 9
2016-06-02 16:06:50.799 XXGCD[37133:347348] 10

4. dispatch_source_set_timer,dispatch_suspend,dispatch_resume,dispatch_source_cancel

`dispatch_suspend`:暂停`dispatch_source_t` 
`dispatch_resume`:恢复`dispatch_source_t`
`dispatch_cancel`:取消`dispatch_source_t`

众所周知,定时器有NSTimer,但是NSTimer有如下弊端:

  1. 必须保证有一个活跃的runloop,子线程的runloop是默认关闭的。这时如果不手动激活runloop,performSelector和scheduledTimerWithTimeInterval的调用将是无效的
  2. NSTimer的创建与撤销必须在同一个线程操作、performSelector的创建与撤销必须在同一个线程操作。
  3. 内存管理有潜在泄露的风险
_timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(doSometing) userInfo:nil repeats:YES];

如果此时不调用

[_timer invalidate];

是无法停止的。还有持有self,造成对象无法释放。所以在此我还是比较推荐用dispatch的timer,无须考虑这些事情

@interface XXGCDTimer()
{
dispatch_source_t _timer;
}
@end

@implementation XXGCDTimer
-(void) startGCDTimer{
NSTimeInterval period = 1.0; //设置时间间隔
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), period * NSEC_PER_SEC, 0); //每秒执行
dispatch_source_set_event_handler(_timer, ^{
//在这里执行事件
NSLog(@"每秒执行test");
});

dispatch_resume(_timer);
}

-(void) pauseTimer{
if(_timer){
dispatch_suspend(_timer);
}
}

-(void) resumeTimer{
if(_timer){
dispatch_resume(_timer);
}
}

-(void) stopTimer{
if(_timer){
dispatch_source_cancel(_timer);
_timer = nil;
}
}
@end

5. dispatch_barrier_async

这里之前的文章有非常详细的解释以及demo在文章后面。可以参考 iOS异步读写总结之NSDictionary ,这里就不去详细说明了。 注意一下 DISPATCH_QUEUE_CONCURRENT (并行队列)和 DISPATCH_QUEUE_SERIAL (串行队列)即可

6. dispatch_syncdispatch_async

这里一个是同步,一个是异步。只要注意一下死锁的问题就好了。

错误场景1: (死锁)结果无法输出

-(void) startSYNC{
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3");
});
}

原因:
1: dispatch_sync 在等待block语句执行完成,而block语句需要在主线程里执行,所以 dispatch_sync 如果在主线程调用就会造成死锁
2: dispatch_sync 是同步的,本身就会阻塞当前线程,也即主线程。而又往主线程里塞进去一个block,所以就会发生死锁。

错误场景2: (死锁)

- (void)testSYNC2 {
dispatch_queue_t aSerialDispatchQueue =
dispatch_queue_create("I.am.an.iOS.developer", DISPATCH_QUEUE_SERIAL);

dispatch_sync(aSerialDispatchQueue, ^{
// block 1
NSLog(@"Good Night, Benjamin.");
dispatch_sync(aSerialDispatchQueue, ^{
// block 2
NSLog(@"Good Night, Daisy.");
});
});
}
```
原因:
简单来说,在dispatch_sync嵌套使用时要注意:不能在一个嵌套中使用同一个serial dispatch queue,因为会发生死锁;

##### 正确用法:

```objc
dispatch_async(dispatch_get_main_queue(), ^{
//async 异步队列,dispatch_async 函数会立即返回, block会在后台异步执行。
NSLog(@"2");//不会造成死锁;
});

7. dispatch_get_global_queue

-(void) test{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 耗时的操作
dispatch_async(dispatch_get_main_queue(), ^{
// 更新界面
NSLog(@"update UI");
});
});
}

全局并发队列的优先级

#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台

注意:
在实际修改bug的时候发现,global全局队列并不会反复创建新的线程,在系统启动后底层会维护一个线程池的概念,一旦用Global的队列结束,会缓存一段时间。因为在测试的时候可以打印出Thread ID。在多线程情况下发现一些ThreadID是一样的,由此得出结论。

8. dispatch_once

// 使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行1次的代码(这里面默认是线程安全的)
});

9. dispatch_after

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2秒后异步执行这里的代码...
});
原文  http://www.iosxxx.com/blog/2016-06-02-GCD那些事.html
正文到此结束
Loading...