iOS-多线程编程学习之GCD——串行队列和并发队列(五)

  Grand Central Dispatch(GCD)有很多部分构成,例如有很好的语言特性,运行库,还提供了系统的、高效的方式来支持具有多核处理器的iOS和OS X设备进行并发事件处理。

  BSD子系统,CoreFoundation和Cocoa APIs都已经使用这个增强特性来进行扩展了,因为它可以使得你的系统和应用运行的更快,更有效率,响应更及时。GCD是运行在系统级别的,他可以更好的满足应用运行时的需求,更合理调度有限的系统资源。
  GCD并不是被限制只能在系统级别应用上使用的,我们可以在更高层的应用上也使用它。但是一般情况下我们还可以考虑Cocoa中是否有类似的对象可以实现相似的功能,例如NSOperation,它也提供了很简单便捷的方式来进行并发任务处理【传送门——iOS-多线程编程学习之NSOperation(四)】。

GCD与ARC

  当使用Objective-C来构建应用时,所有的dispatch obeject都是Objective-C类型的对象。所以当在应用中开启了ARC(自动引用计数)时,这些dispatch obeject也会想OC的对象一样被自动的发送retainrelease消息。当ARC被禁用时(也就是进行MRC手动内存管理时),可以通过使用dispatch_retaindispatch_release来实现类似OC对象的retain和release操作。

创建任务队列

GCD中的队列主要分两种。

  • 并发队列:允许多个任务同时执行。
  • 串行队列:任务按顺序依次执行。

  系统默认有一个和主线程绑定的串行队列,还有一个全局的并发队列。这两个队列都可以通过响应的方法来获得。

获得主线程的串行队列

调用这个方法获取主线程队列时,该队列是由主线程来自动创建的。

dispatch_queue_t mainQueue = dispatch_get_main_queue();

获得全局的并发队列

  获取全局并发队列的方法中有两个参数,第二个参数暂时没有使用,默认传值0就好。第一个参数表示队列的优先级,其中优先级主要分为4种。

  • DISPATCH_QUEUE_PRIORITY_HIGH 2,优先级高
  • DISPATCH_QUEUE_PRIORITY_DEFAULT 0,默认优先级,中
  • DISPATCH_QUEUE_PRIORITY_LOW -2,优先级低
  • DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN,后台模式,优先级最低

    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    

创建自定义的队列

  创建自定义的队列主要有两种,一种是串行队列,一种是并行队列。在创建自定义队列时会需要传两个参数,第一个为队列的名称,第二个为表示队列类型的常量(串行还是并行)。

DISPATCH_QUEUE_SERIAL //串行队列
DISPATCH_QUEUE_CONCURRENT //并行队列

代码也很简单。

//串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.lysongzi.serial", DISPATCH_QUEUE_SERIAL);

//并行队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.lysongzi.concurrent", DISPATCH_QUEUE_CONCURRENT);

同步和异步

  当我们创建了队列之后,我们需要把任务添加到队列中,并指定以同步还是异步的方式执行添加到队列中的任务。
  同步,其会在当前线程立即执行添加的任务(无论是串行还是并行队列都如此)。
  异步,其会新创建一个新的线程来执行任务。而异步对于串行和并行队列的又不一样的意义的。
  对于异步执行的串行队列的话,新添加的多个任务会在新创建的线程中依次执行,即一个执行完在执行另一个任务,有点类似同步执行的样子(其区别可以看做是同步不会创建新的线程,而异步会创建新的线程,且只创建一个)。我们可以看一下例子。

-(void)testSerialQueue
{
    //创建串行队列,传入参数为DISPATCH_QUEUE_SERIAL
    self.serialQueue = dispatch_queue_create("com.lysongzi.serial", DISPATCH_QUEUE_SERIAL);

    //同步执行队列中的任务,会立即在当前线程执行该任务
    dispatch_sync(self.serialQueue, ^{
        NSLog(@"这里是同步执行的串行队列01。===> %@", [NSThread currentThread]);
    });

    //异步执行任务,会新开一个线程,多个任务按顺序执行
    dispatch_async(self.serialQueue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"串行01 ===> %d ===> %@", i, [NSThread currentThread]);
        }
    });

    dispatch_sync(self.serialQueue, ^{
        NSLog(@"这里是同步执行的串行队列02。===> %@", [NSThread currentThread]);
    });

    dispatch_async(self.serialQueue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"串行02 ===> %d ===> %@", i, [NSThread currentThread]);
        }
    });

    dispatch_async(self.serialQueue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"串行03 ===> %d ===> %@", i, [NSThread currentThread]);
        }
    });
}

  然后我们看一下输出结果。我们会发现使用同步方式执行的任务都会被立即执行,并且是在当前线程(主线程)中执行的。而用异步方式的任务则会在新创建的线程(number=2)中执行,并且它们是按照一定顺序执行的(串行01->串行02->串行03)。

2016-02-26 22:17:34.745 GCDDemon[4084:422492] 这里是同步执行的串行队列01。===> <NSThread: 0x100103080>{number = 1, name = main}
2016-02-26 22:17:34.747 GCDDemon[4084:422514] 串行01 ===> 0 ===> <NSThread: 0x100103a70>{number = 2, name = (null)}
2016-02-26 22:17:34.747 GCDDemon[4084:422514] 串行01 ===> 1 ===> <NSThread: 0x100103a70>{number = 2, name = (null)}
2016-02-26 22:17:34.747 GCDDemon[4084:422514] 串行01 ===> 2 ===> <NSThread: 0x100103a70>{number = 2, name = (null)}
2016-02-26 22:17:34.748 GCDDemon[4084:422492] 这里是同步执行的串行队列02。===> <NSThread: 0x100103080>{number = 1, name = main}
2016-02-26 22:17:34.748 GCDDemon[4084:422514] 串行02 ===> 0 ===> <NSThread: 0x100103a70>{number = 2, name = (null)}
2016-02-26 22:17:34.748 GCDDemon[4084:422514] 串行02 ===> 1 ===> <NSThread: 0x100103a70>{number = 2, name = (null)}
2016-02-26 22:17:34.748 GCDDemon[4084:422514] 串行02 ===> 2 ===> <NSThread: 0x100103a70>{number = 2, name = (null)}
2016-02-26 22:17:34.748 GCDDemon[4084:422514] 串行03 ===> 0 ===> <NSThread: 0x100103a70>{number = 2, name = (null)}
2016-02-26 22:17:34.748 GCDDemon[4084:422514] 串行03 ===> 1 ===> <NSThread: 0x100103a70>{number = 2, name = (null)}
2016-02-26 22:17:34.748 GCDDemon[4084:422514] 串行03 ===> 2 ===> <NSThread: 0x100103a70>{number = 2, name = (null)}

  我们再来看看如果是并发队列的话,使用同步方式执行任何则和串行队列一样。而使用异步方式执行任务的话,新添加的任务都会放到新创建的线程,即每个任务在单独线程中,并且所有的任务都是并发执行的,而不像串行队列是有顺序的。如果是异步方式执行的话,则每个任务都会新开一个线程,并且这些任务是并发执行的。我们来看一下下面这个例子。

-(void)testConcurrentQueue
{
    //创建并发队列,传入参数为DISPATCH_QUEUE_CONCURRENT
    self.concurrentQueue = dispatch_queue_create("com.lysongzi.concurrent", DISPATCH_QUEUE_CONCURRENT);

    //同步执行队列中的任务,会立即执行该任务
    dispatch_sync(self.concurrentQueue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"并发同步01 ===> %d ===> %@", i, [NSThread currentThread]);
        }
    });

    dispatch_sync(self.concurrentQueue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"并发同步02 ===> %d ===> %@", i, [NSThread currentThread]);
        }
    });

    //异步执行并发队列
    dispatch_async(self.concurrentQueue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"并发异步01 ===> %d ===> %@", i, [NSThread currentThread]);
        }
    });

    dispatch_async(self.concurrentQueue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"并发异步02 ===> %d ===> %@", i, [NSThread currentThread]);
        }
    });

    dispatch_async(self.concurrentQueue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"并发异步03 ===> %d ===> %@", i, [NSThread currentThread]);
        }
    });
}

然后我们来看一下输出结果。这刚好验证了我们的说法。

2016-02-26 22:43:17.553 GCDDemon[4223:436777] 并发同步01 ===> 0 ===> <NSThread: 0x100108c80>{number = 1, name = main}
2016-02-26 22:43:17.554 GCDDemon[4223:436777] 并发同步01 ===> 1 ===> <NSThread: 0x100108c80>{number = 1, name = main}
2016-02-26 22:43:17.554 GCDDemon[4223:436777] 并发同步01 ===> 2 ===> <NSThread: 0x100108c80>{number = 1, name = main}
2016-02-26 22:43:17.554 GCDDemon[4223:436777] 并发同步02 ===> 0 ===> <NSThread: 0x100108c80>{number = 1, name = main}
2016-02-26 22:43:17.554 GCDDemon[4223:436777] 并发同步02 ===> 1 ===> <NSThread: 0x100108c80>{number = 1, name = main}
2016-02-26 22:43:17.554 GCDDemon[4223:436777] 并发同步02 ===> 2 ===> <NSThread: 0x100108c80>{number = 1, name = main}
2016-02-26 22:43:17.555 GCDDemon[4223:436826] 并发异步03 ===> 0 ===> <NSThread: 0x100700110>{number = 3, name = (null)}
2016-02-26 22:43:17.555 GCDDemon[4223:436826] 并发异步03 ===> 1 ===> <NSThread: 0x100700110>{number = 3, name = (null)}
2016-02-26 22:43:17.555 GCDDemon[4223:436825] 并发异步02 ===> 0 ===> <NSThread: 0x1002000a0>{number = 2, name = (null)}
2016-02-26 22:43:17.555 GCDDemon[4223:436826] 并发异步03 ===> 2 ===> <NSThread: 0x100700110>{number = 3, name = (null)}
2016-02-26 22:43:17.555 GCDDemon[4223:436825] 并发异步02 ===> 1 ===> <NSThread: 0x1002000a0>{number = 2, name = (null)}
2016-02-26 22:43:17.555 GCDDemon[4223:436826] 并发异步03 ===> 3 ===> <NSThread: 0x100700110>{number = 3, name = (null)}
2016-02-26 22:43:17.555 GCDDemon[4223:436825] 并发异步02 ===> 2 ===> <NSThread: 0x1002000a0>{number = 2, name = (null)}
2016-02-26 22:43:17.555 GCDDemon[4223:436826] 并发异步03 ===> 4 ===> <NSThread: 0x100700110>{number = 3, name = (null)}
2016-02-26 22:43:17.556 GCDDemon[4223:436824] 并发异步01 ===> 0 ===> <NSThread: 0x10010aec0>{number = 4, name = (null)}
2016-02-26 22:43:17.582 GCDDemon[4223:436825] 并发异步02 ===> 3 ===> <NSThread: 0x1002000a0>{number = 2, name = (null)}
2016-02-26 22:43:17.582 GCDDemon[4223:436824] 并发异步01 ===> 1 ===> <NSThread: 0x10010aec0>{number = 4, name = (null)}
2016-02-26 22:43:17.582 GCDDemon[4223:436825] 并发异步02 ===> 4 ===> <NSThread: 0x1002000a0>{number = 2, name = (null)}
2016-02-26 22:43:17.582 GCDDemon[4223:436824] 并发异步01 ===> 2 ===> <NSThread: 0x10010aec0>{number = 4, name = (null)}
2016-02-26 22:43:17.582 GCDDemon[4223:436824] 并发异步01 ===> 3 ===> <NSThread: 0x10010aec0>{number = 4, name = (null)}
2016-02-26 22:43:17.582 GCDDemon[4223:436824] 并发异步01 ===> 4 ===> <NSThread: 0x10010aec0>{number = 4, name = (null)}

源代码

Github-GCDDemon

参考资料

  1. 苹果官方文档-Grand Central Dispatch (GCD) Reference
  2. GCD实践——串行队列/并发队列与iOS多线程详解
  3. iOS开发–四种多线程技术方案