iOS多线程编程之GCD

作者: koce_zhao | 来源:发表于2016-07-07 20:13 被阅读83次

GCD(Grand Central Dispatch)是iOS多任务的核心,它可以让程序员不用直接参与到线程的创建和维护中,而让操作系统来直接处理线程的创建和管理,让多任务的开发更加简单。

操作与队列

在GCD中,操作就是一个代码块(block),而队列(dispatch_queue)就是存放这些操作的地方,要注意,队列并不是线程,队列是组织操作的,调用dispatch_async并不会让操作开始执行,而只是把操作添加到队列的末尾,当操作到达队列头时,操作才会开始执行。添加到队列中的操作不能取消。

创建队列

使用dispatch_queue_create(constchar *label,dispatch_queue_attr_t attr)来创建队列,第一个参数是队列的标示符,第二个参数则指定队形是串行队列(DISPATCH_QUEUE_SERIAL)还是并发队列(DISPATCH_QUEUE_CONCURRENT),不管是串行队列还是并发队列,队列中的操作都采用FIFO的方式取出执行,不同的是并发队列可以将队列中的多个操作取出并放到不同的线程中执行。

获取队列

可以使用dispatch_get_main_queue()得到主队列,主队列是一个串行队列,队列中的所有操作都是在主线程中执行的
系统中存在有全局并发队列,使用dispatch_get_global_queue(long identifier, unsigned long flags)获取全局并发队列,使用第一个参数选择优先级:

#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

GCD会根据可用线程尽可能的从高优先级队列取出操作并执行,等高优先级队列空了以后,继续从默认优先级队列中取出操作并执行,以此类推。

执行操作

操作有两种执行方式:同步执行异步执行
使用dispatch_sync(dispatch_queue_t queue, dispatch_block_t block)进行同步执行操作,使用dispatch_async(dispatch_queue_t queue,dispatch_block_t block)进行异步执行操作。
使用异步执行操作,系统会将block添加到队列queue中,而如果使用同步执行操作,系统则会先阻塞当前的线程,再将block添加到队列queue中,等block执行完了,当前线程才会取消阻塞。需要注意同步执行很容易会出现死锁的情况:

dispatch_sync(dispatch_get_main_queue(), ^{
    //同步执行
    NSLog(@"sync");
});

这段代码看似没有问题,但如果当前线程是主线程的话,则会先阻塞主线程,等待block执行完,而block却是在主线中执行的,所以block中的代码永远不会被执行,而且主线程也会被一直阻塞。

暂停队列

可以使用dispatch_suspend(dispatch_object_t object)来暂停队列,要注意,正在执行的操作(block)不会被暂停,只会暂停还未开始执行的操作;对应的,使用dispatch_resume(dispatch_object_t object)来恢复执行队列。多次暂停队列需要等量的恢复操作。

队列组

队列组(dispatch_group_t)可以将任务进行分组并执行,当这组任务都执行完后,调用者可以收到通知。
使用dispatch_group_create()创建队列组;
使用dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);将把block添加到queue中异步执行,并表示block为group组中的操作;
使用dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);接收group中所有操作执行完成的通知(不会阻塞当前线程)。
使用dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);先阻塞当前线程,等待group中的所有操作执行完后,继续阻塞当前线程timeout大小的时间。

例如:

dispatch_group_t group = dispatch_group_create(); 
dispatch_queue_t queue1 = dispatch_queue_create("1", DISPATCH_QUEUE_CONCURRENT);  
dispatch_queue_t queue2 = dispatch_queue_create("2", DISPATCH_QUEUE_CONCURRENT);  
dispatch_queue_t queue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);  
NSLog(@"开始");  
dispatch_group_async(group, queue1, ^{  

for (int i = 0; i < 3; i++) {  
    NSLog(@"操作1-%d", i);  
}
});  
dispatch_group_async(group, queue2, ^{  
for (int i = 0; i < 3; i++) {  
    NSLog(@"操作2-%d", i);  
}
});dispatch_group_async(group, queue3, ^{  
for (int i = 0; i < 3; i++) {  
    NSLog(@"操作3-%d", i);  
}
});  
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"更新UI");
});  

上面先创建了一个队列组和三个并发队列,再将三个并行任务加入队列组中并执行,执行完后通知主队列更新UI。

打印结果:

015-08-12 14:28:08.275 GCDTest[2248:88609] 开始  
2015-08-12 14:28:08.276 GCDTest[2248:88635] 操作1-0
2015-08-12 14:28:08.277 GCDTest[2248:88635] 操作1-1  
2015-08-12 14:28:08.277 GCDTest[2248:88635] 操作1-2  
2015-08-12 14:28:08.277 GCDTest[2248:88635] 操作2-0  
2015-08-12 14:28:08.277 GCDTest[2248:88635] 操作2-1  
2015-08-12 14:28:08.277 GCDTest[2248:88635] 操作2-2  
2015-08-12 14:28:08.277 GCDTest[2248:88635] 操作3-0  
2015-08-12 14:28:08.277 GCDTest[2248:88635] 操作3-1  
2015-08-12 14:28:08.278 GCDTest[2248:88635] 操作3-2  
2015-08-12 14:28:08.286 GCDTest[2248:88609] 更新UI

GCD的应用

使用GCD代替同步锁

在开发中,如果有多个线程执行同一代码,可能会出现一些问题,而解决方案就是给代码添加同步锁。在GCD出现以前,可以使用@synchronized(object)给object创建一个锁,这种方法很容易使用,但是滥用会造成代码效率低下。而使用GCD可以以更简单、更高效的方式给代码加锁。比如在setter和getter中:

- (NSString *)aString  
{  
    __block NSString *str;  

    dispatch_sync(_myQueue, ^{  
        str = _aString;  
    });
    return str;
}  

- (void)setAString:(NSString *)aString  
{  
    dispatch_barrier_async(_myQueue, ^{  
        _aString = aString;  
    });
}

其中_myQueue是使用自己创建的并发队列;
在getter方法里将操作放入_myQueue里同步执行,可以保证获取方法在操作执行完成后才能再次被使用;而dispatch_barrier_async可以将_myQueue阻塞,并在操作执行完成后再取消阻塞,这样可以保证在执行设置方法时不能执行获取方法。

使用GCD实现单例模式

使用GCD可以更简单的实现单例模式,解决方法就是dispatch_once。具体用法就是:

static GCDTest *test;  
+(id)sharedInstance  
{  
  static dispatch_once_t once;  
  dispatch_once(&once, ^{
    test = [[GCDTest alloc] init];  
  });  
  return test;
}

因为每次调用时都需要使用相同的dispatch_once_t,所以需要声明为static。
使用dispatch_once可以保证线程安全,并且简化代码。使用dispatch_once也更高效,它没有使用重量级的同步机制,而是采用“原子访问”(atomic access)来查询dispatch_once_t来判断其所对应的代码是否已执行过。




GCD的内容与用法远远不止这些,有兴趣的可以在开发者文档中查询。

相关文章

  • iOS多线程相关面试题

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

  • 多线程之--NSOperation

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

  • iOS多线程之--NSThread

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

  • iOS多线程之NSOperations

    相关文章:iOS多线程之NSThreadiOS多线程之GCD NSOperation(任务)与NSOperatio...

  • iOS多线程之GCD

    iOS多线程之GCD 什么是GCD GCD(grand central dispatch) 是 libdispat...

  • iOS进阶之多线程管理(GCD、RunLoop、pthread、

    深入理解RunLoopiOS多线程--彻底学会多线程之『GCD』iOS多线程--彻底学会多线程之『pthread、...

  • 多线程

    参考文章:iOS多线程--彻底学会多线程之『GCD』Swift 3.0 GCD和DispatchQueue 使用解...

  • GCD的使用

    GCD的详解iOS多线程--彻底学会多线程之『GCD』 GCD线程之间的通讯 一般在主线程里面刷新UI 点击、滚动...

  • iOS开发多线程之GCD

    iOS开发多线程之GCDiOS开发之GCD同步任务加强iOS开发之GCD串行队列iOS开发之GCD并发队列 GCD...

  • iOS开发之GCD并发队列

    iOS开发多线程之GCDiOS开发之GCD同步任务加强iOS开发之GCD串行队列iOS开发之GCD并发队列 03 ...

网友评论

    本文标题:iOS多线程编程之GCD

    本文链接:https://www.haomeiwen.com/subject/jopljttx.html