iOS开发多线程--GCD

GCD简介

Grand Central Dispatch (GCD)是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并行任务。在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用。 -----百度百科

GCD好处

  • GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • GCD更接近底层,性能较高

GCD的两个概念

1.任务
任务就是我们实际要执行的代码块。而执行这代码的方式有两种:同步执行(sync)异步执行(async)

  • 同步执行(sync)
    • 不具备开启新线程的能力,只能在当前线程中执行任务
    • 同步任务添加到队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后才会继续执行
  • 异步执行(async)
    • 可以开启新线程,可以在新线程中执行任务
    • 异步任务添加到队列中,会继续执行任务

异步执行async虽然有开启新线程的能力,但是不一定会开启新线程,这根任务所指定的队列类型有关。

2.队列
队列是指执行任务的等待队列,即用来存放任务的队列。队列遵循FIFO(先进先出),新任务总是插到队列的末尾,读取任务总是从队列的头部开始读取,没读取一个任务,就会释放一个任务。GCD中,有两种队列:串行队列并发队列,两者都遵循FIFO原则。

  • 串行队列(Serial Dispatch Queue)
    • 每次只有一个任务被执行,让任务一个接着一个执行。(只开启一个线程)
  • 并发队列(Concurrent Dispatch Queue)
    • 可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)

并发队列的并发功能只有在异步(dispatch_async)函数下才有效

GCD使用步骤

1.创建队列(串行或并发)

dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);

label的参数表示队列的唯一标识符,用于debug,可以传空,推荐使用域名反写,attr用来决定是串行队列,还是并发队列。 DISPATCH_QUEUE_SERIAL表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并发队列。

dispatch_queue_t queue = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_CONCURRENT);
  • 串行队列,GCD提供了一个特殊的串行队列:主队列(Main Dispatch Queue)
    • 所有放在主队列中任务,都会放到主线程中执行
    • 可以使用dispatch_get_main_queue()来获取主队列
// 主队列的获取方法
dispatch_queue_t queue = dispatch_get_main_queue();
  • 并发队列,GCD提供了全局并发队列(Global Dispatch Queue)
    • 可以使用dispatch_get_global_queue来获取。
dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);

identifier的优先级从高到低:
DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND

flags:默认传0

2.将任务添加到任务的等待队列中
GCD提供了同步执行任务的创建方法dispatch_sync和异步执行任务的创建方法dispatch_async

// 同步执行任务创建方法
dispatch_sync(queue, ^{
    // 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
    // 这里放异步执行任务代码
});

两种因素相互组合有四种组合方式

同步 + 并发
异步 + 并发
同步 + 串行
异步 + 串行

但是加上说的两种特殊队列:全局并发队列,主队列。全局并发队列可以当成普通的并发队列来处理,方式主队列有点特殊,需要额外处理,这就多了两种方式。

同步 + 主队列
异步 + 主队列

组合效果如下表格

并发队列 串行队列 主队列
同步 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 主线程调用:死锁卡住
其他线程调用:没有开发新线程,串行执行任务
异步 开启新线程,并发执行任务 开启新线程,串行执行任务 没有开启新线程,串行执行任务

GCD的使用

1.同步+并发

/**
 同步并发执行
 在当前线程执行任务,不会开启新线程,执行完一个任务,在执行下一个任务
 */
- (void)syncWithConcurrent{
    NSLog(@"currentThread -- %@", [NSThread currentThread]);
    
    dispatch_queue_t queueu = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_sync(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"111----%@", [NSThread currentThread]);
        }
    });
    //追加任务
    dispatch_sync(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"222----%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"333----%@", [NSThread currentThread]);
        }
    });
}

2018-05-16 14:38:55.788806+0800 MultiThread[5657:399983] currentThread -- {number = 1, name = main}
2018-05-16 14:38:55.789158+0800 MultiThread[5657:399983] begin
2018-05-16 14:38:56.790163+0800 MultiThread[5657:399983] 111----{number = 1, name = main}
2018-05-16 14:38:57.790860+0800 MultiThread[5657:399983] 111----{number = 1, name = main}
2018-05-16 14:38:58.791470+0800 MultiThread[5657:399983] 222----{number = 1, name = main}
2018-05-16 14:38:59.794281+0800 MultiThread[5657:399983] 222----{number = 1, name = main}
2018-05-16 14:39:00.795110+0800 MultiThread[5657:399983] 333----{number = 1, name = main}
2018-05-16 14:39:01.795762+0800 MultiThread[5657:399983] 333----{number = 1, name = main}
2018-05-16 14:39:01.796005+0800 MultiThread[5657:399983] end

从上述例子中可以看到

  • 所有的任务都是在当前线程(主线程)中执行的。并没有开启新线程。
  • 按顺序执行任务。并发队列虽然可以开启多个线程,同时执行任务,但是不能创建新线程。所以并发并不存在,实际效果相当于串行。

2.异步 + 并发

/**
 可以开启多个线程,交替执行任务
 */
- (void)asyncConcurrent{
    NSLog(@"currentThread -- %@", [NSThread currentThread]);
    
    NSLog(@"begin");
    
    dispatch_queue_t queueu = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"111----%@", [NSThread currentThread]);
        }
    });
    //追加任务
    dispatch_async(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"222----%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"333----%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"end");
}

2018-05-16 14:45:25.548159+0800 MultiThread[5724:408572] currentThread -- {number = 1, name = main}
2018-05-16 14:45:25.548681+0800 MultiThread[5724:408572] begin
2018-05-16 14:45:25.548907+0800 MultiThread[5724:408572] end
2018-05-16 14:45:26.549549+0800 MultiThread[5724:408638] 111----{number = 5, name = (null)}
2018-05-16 14:45:26.549660+0800 MultiThread[5724:408804] 333----{number = 7, name = (null)}
2018-05-16 14:45:26.549679+0800 MultiThread[5724:408803] 222----{number = 6, name = (null)}
2018-05-16 14:45:27.550524+0800 MultiThread[5724:408803] 222----{number = 6, name = (null)}
2018-05-16 14:45:27.550526+0800 MultiThread[5724:408804] 333----{number = 7, name = (null)}
2018-05-16 14:45:27.550526+0800 MultiThread[5724:408638] 111----{number = 5, name = (null)}

从上述例子可以看出:

  • 除了当前线程(主线程),系统有开启了三个线程,任务交替执行。
  • begin和end先打印了,说明线程并没有等待,开启了新线程,在新线程中执行任务。

3.同步 + 串行

/**
 不会创建新的线程,在当前线程执行,任务串行执行
 */
- (void)syncSerial{
    NSLog(@"currentThread -- %@", [NSThread currentThread]);
    
    NSLog(@"begin");
    
    dispatch_queue_t queueu = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"111----%@", [NSThread currentThread]);
        }
    });
    //追加任务
    dispatch_sync(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"222----%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"333----%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"end");
}

2018-05-16 14:59:32.394332+0800 MultiThread[5889:426076] currentThread -- {number = 1, name = main}
2018-05-16 14:59:32.394566+0800 MultiThread[5889:426076] begin
2018-05-16 14:59:33.395773+0800 MultiThread[5889:426076] 111----{number = 1, name = main}
2018-05-16 14:59:34.397207+0800 MultiThread[5889:426076] 111----{number = 1, name = main}
2018-05-16 14:59:35.398690+0800 MultiThread[5889:426076] 222----{number = 1, name = main}
2018-05-16 14:59:36.399925+0800 MultiThread[5889:426076] 222----{number = 1, name = main}
2018-05-16 14:59:37.400594+0800 MultiThread[5889:426076] 333----{number = 1, name = main}
2018-05-16 14:59:38.401101+0800 MultiThread[5889:426076] 333----{number = 1, name = main}
2018-05-16 14:59:38.401345+0800 MultiThread[5889:426076] end

从上述例子可以看出:

  • 所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(同步执行不具备开启新线程的能力)
  • 需要等待队列任务的任务完成
  • 任务按顺序执行

4.异步 + 串行

/**
 创建新线程,但是任务是串行的,会一个一个执行
 */
- (void)asyncSerial{
    NSLog(@"currentThread -- %@", [NSThread currentThread]);
    
    NSLog(@"begin");
    
    dispatch_queue_t queueu = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"111----%@", [NSThread currentThread]);
        }
    });
    //追加任务
    dispatch_async(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"222----%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"333----%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"end");
}

2018-05-16 15:08:48.563433+0800 MultiThread[6045:441312] currentThread -- {number = 1, name = main}
2018-05-16 15:08:48.563794+0800 MultiThread[6045:441312] begin
2018-05-16 15:08:48.564524+0800 MultiThread[6045:441312] end
2018-05-16 15:08:49.569620+0800 MultiThread[6045:441364] 111----{number = 5, name = (null)}
2018-05-16 15:08:50.570245+0800 MultiThread[6045:441364] 111----{number = 5, name = (null)}
2018-05-16 15:08:51.574290+0800 MultiThread[6045:441364] 222----{number = 5, name = (null)}
2018-05-16 15:08:52.578832+0800 MultiThread[6045:441364] 222----{number = 5, name = (null)}
2018-05-16 15:08:53.580927+0800 MultiThread[6045:441364] 333----{number = 5, name = (null)}
2018-05-16 15:08:54.586438+0800 MultiThread[6045:441364] 333----{number = 5, name = (null)}

从上述例子可以看出:

  • 开启了一个新的线程
  • 所有任务都是在begin和end打印后执行的
  • 任务是按顺序执行的

5.同步 + 主队列

  1. 在主线程中调用
/**
 卡死
 */
- (void)syncMain{
    NSLog(@"currentThread -- %@", [NSThread currentThread]);
    
    NSLog(@"begin");
    
    dispatch_queue_t queueu = dispatch_get_main_queue();
    
    dispatch_sync(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"111----%@", [NSThread currentThread]);
        }
    });
    //追加任务
    dispatch_sync(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"222----%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"333----%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"end");
}

2018-05-16 15:15:40.866943+0800 MultiThread[6145:451264] currentThread -- {number = 1, name = main}
2018-05-16 15:15:40.867132+0800 MultiThread[6145:451264] begin
(lldb)

我们将syncMain()方法放到主线程的队列中执行,而syncMain()方法中,又在主线程中追加了打印任务。并发情况下,syncMain()在等值打印任务完成,而打印任务又在等着syncMain()的完成,相互等待,导致卡死。

  1. 在其他线程中调用
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];

2018-05-16 15:21:21.114561+0800 MultiThread[6228:461243] currentThread -- {number = 5, name = (null)}
2018-05-16 15:21:21.114724+0800 MultiThread[6228:461243] begin
2018-05-16 15:21:22.153793+0800 MultiThread[6228:461100] 111----{number = 1, name = main}
2018-05-16 15:21:23.154493+0800 MultiThread[6228:461100] 111----{number = 1, name = main}
2018-05-16 15:21:24.156163+0800 MultiThread[6228:461100] 222----{number = 1, name = main}
2018-05-16 15:21:25.157599+0800 MultiThread[6228:461100] 222----{number = 1, name = main}
2018-05-16 15:21:26.159337+0800 MultiThread[6228:461100] 333----{number = 1, name = main}
2018-05-16 15:21:27.160866+0800 MultiThread[6228:461100] 333----{number = 1, name = main}
2018-05-16 15:21:27.161208+0800 MultiThread[6228:461243] end

上述例子可以看出:

  • 所有任务都是在主线程中执行,没有开启新线程
  • 同步执行任务都是需要等待队列的任务执行结束
  • 串行队列,每次只执行一个任务

因为syncMain()任务放在了新开启的线程中,而打印任务都追加在主队列中,此时主队列并没有其他执行的任务,所以可以直接执行打印任务。

6.异步 + 主队列

/**
 只在主线程中执行任务,执行完一个任务,再执行下一个任务
 */
- (void)asyncMain{
    NSLog(@"currentThread -- %@", [NSThread currentThread]);
    
    NSLog(@"begin");
    
    dispatch_queue_t queueu = dispatch_get_main_queue();
    
    dispatch_async(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"111----%@", [NSThread currentThread]);
        }
    });
    //追加任务
    dispatch_async(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"222----%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"333----%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"end");
}

018-05-16 15:28:29.018407+0800 MultiThread[6308:470456] currentThread -- {number = 1, name = main}
2018-05-16 15:28:29.018601+0800 MultiThread[6308:470456] begin
2018-05-16 15:28:29.018752+0800 MultiThread[6308:470456] end

2018-05-16 15:28:30.056933+0800 MultiThread[6308:470456] 111----{number = 1, name = main}
2018-05-16 15:28:31.057944+0800 MultiThread[6308:470456] 111----{number = 1, name = main}
2018-05-16 15:28:32.059107+0800 MultiThread[6308:470456] 222----{number = 1, name = main}
2018-05-16 15:28:33.060579+0800 MultiThread[6308:470456] 222----{number = 1, name = main}
2018-05-16 15:28:34.062021+0800 MultiThread[6308:470456] 333----{number = 1, name = main}
2018-05-16 15:28:35.063583+0800 MultiThread[6308:470456] 333----{number = 1, name = main}

从上述例子中可以看出:

  • 所有任务都是在主队列中执行
  • 异步执行不会做人接等待,可以继续执行任务
  • 任务按顺序执行的,主队列是串行队列

GCD线程之间的通信

在其他线程中处理下载图片,请求数据等操作,在主线程中刷新UI,数据,就需要用到线程之间的通讯。

- (void)communication{
    dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t main = dispatch_get_main_queue();
    
    dispatch_async(global, ^{
        for (int i = 0; i < 3; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1--- %@", [NSThread currentThread]);
        }
        
        dispatch_async(main, ^{
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2---%@", [NSThread currentThread]);
        });
    });
}

2018-05-16 15:47:23.444937+0800 MultiThread[6516:498644] 1--- {number = 5, name = (null)}
2018-05-16 15:47:25.450123+0800 MultiThread[6516:498644] 1--- {number = 5, name = (null)}
2018-05-16 15:47:27.455633+0800 MultiThread[6516:498644] 1--- {number = 5, name = (null)}
2018-05-16 15:47:29.456492+0800 MultiThread[6516:498523] 2---{number = 1, name = main}

从上述例子中可以看出:

  • 可以看到在其他线程中先执行任务,执行完了之后回到主线程执行主线程的相应操作。

GCD的其他方法

1.栅栏方法(dispatch_barrier_async)

如果我们需要实现异步请求两个数据,同时B请求必须在A请求完成后才能继续请求,这就需要一种手段分割开A和B请求,这就是栅栏方法dispatch_barrier_asyncdispatch_barrier_async方法会等待前面添加到并发队列中的任务完成后,截止执行dispatch_barrier_async的block任务,最后回复正常的异步并发操作。

- (void)barrier{
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        // 追加任务1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    
    dispatch_barrier_async(queue, ^{
        // 追加任务 barrier
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    
    dispatch_async(queue, ^{
        // 追加任务3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务4
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"4---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
}

2018-05-16 16:08:35.729731+0800 MultiThread[6772:531874] 1---{number = 5, name = (null)}
2018-05-16 16:08:35.729844+0800 MultiThread[6772:531870] 2---{number = 6, name = (null)}
2018-05-16 16:08:37.732856+0800 MultiThread[6772:531870] 2---{number = 6, name = (null)}
2018-05-16 16:08:37.732856+0800 MultiThread[6772:531874] 1---{number = 5, name = (null)}
2018-05-16 16:08:39.735677+0800 MultiThread[6772:531870] barrier---{number = 6, name = (null)}
2018-05-16 16:08:41.736375+0800 MultiThread[6772:531870] barrier---{number = 6, name = (null)}
2018-05-16 16:08:43.740706+0800 MultiThread[6772:531870] 3---{number = 6, name = (null)}
2018-05-16 16:08:43.740710+0800 MultiThread[6772:531874] 4---{number = 5, name = (null)}
2018-05-16 16:08:45.741610+0800 MultiThread[6772:531870] 3---{number = 6, name = (null)}
2018-05-16 16:08:45.741610+0800 MultiThread[6772:531874] 4---{number = 5, name = (null)}

上述例子可以看出:

  • 在执行完栅栏前面的操作之后,才执行栅栏操作,最后再执行栅栏后边的操作

2.延迟执行方法(dispatch_after)

/**
 * 延时执行方法 dispatch_after
 */
- (void)after {
    NSLog(@"currentThread---%@", [NSThread currentThread]);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"after---%@", [NSThread currentThread]);
    });
}

2018-05-16 16:16:26.406706+0800 MultiThread[6866:543950] currentThread---{number = 1, name = main}
2018-05-16 16:16:28.600856+0800 MultiThread[6866:543950] after---{number = 1, name = main}

可以看出来,实际还是存在误差的,所以要求不严格可以说还是用这个方法。

3.GCD一次性代码(dispatch_once)
创建单例、或者需要整个程序运行期间只执行一次的代码,我们就用到了dispatch_once方法。

- (void)once {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 这里的代码指挥执行一次
    });
}

4.循环遍历方法(dispatch_apply)

dispatch_apply方法会开启多个线程来异步遍历数据,无论串行队列,还是并发队列,该方法都会等待全部任务完毕,有点像同步操作。

- (void)apply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    NSLog(@"apply---begin");
    dispatch_apply(5, queue, ^(size_t index) {
        NSLog(@"%zd---%@",index, [NSThread currentThread]);
    });
    NSLog(@"apply---end");
}

2018-05-16 16:34:57.901523+0800 MultiThread[7186:573598] apply---begin
2018-05-16 16:34:57.901834+0800 MultiThread[7186:573598] 0---{number = 1, name = main}
2018-05-16 16:34:57.901899+0800 MultiThread[7186:573672] 1---{number = 5, name = (null)}
2018-05-16 16:34:57.901937+0800 MultiThread[7186:573662] 2---{number = 6, name = (null)}
2018-05-16 16:34:57.901974+0800 MultiThread[7186:573665] 3---{number = 7, name = (null)}
2018-05-16 16:34:57.902303+0800 MultiThread[7186:573598] 4---{number = 1, name = main}
2018-05-16 16:34:57.902560+0800 MultiThread[7186:573598] apply---end

5.GCD队列组(dispatch_group)

有时候会遇到这种情况,要求异步执行两个耗时任务,然后当两个任务完成后调回带主线程,执行主线程的任务。dispatch_group可以把相关的任务归并到一个组内来执行,通过监听组内所有任务的执行情况来做相应处理。

5.1 dispatch_group_create
用于创建任务组

dispatch_group_t dispatch_group_create(void);

5.2 dispatch_group_async
把异步任务提交到指定的任务组和指定的队列执行

void dispatch_group_async(dispatch_group_t group,
                          dispatch_queue_t queue,
                          dispatch_block_t block);
  • group ——对应的任务组,之后可以通过dispatch_group_wait或者dispatch_group_notify监听任务组内任务的执行情况
  • queue ——block任务执行的线程队列,任务组内不同任务的队列可以不同
  • block —— 执行任务的block

5.3 dispatch_group_enter
用于添加对应任务组中的未执行完毕的任务数,执行一次,未执行完毕的任务数加1,当未执行完毕任务数为0的时候,才会使dispatch_group_wait解除阻塞和dispatch_group_notify的block执行

void dispatch_group_enter(dispatch_group_t group);

dispatch_group_leave
用于减少任务组中的未执行完毕的任务数,执行一次,未执行完毕的任务数减1,dispatch_group_enterdispatch_group_leave要匹配,不然系统会认为group任务没有执行完毕

void dispatch_group_leave(dispatch_group_t group);

5.4 dispatch_group_wait
等待组任务完成,会阻塞当前线程,当任务组执行完毕时,才会解除阻塞当前线程

long dispatch_group_wait(dispatch_group_t group, 
                         dispatch_time_t timeout);
  • group ——需要等待的任务组
  • timeout ——等待的超时时间(即等多久),单位为dispatch_time_t。如果设置为DISPATCH_TIME_FOREVER,则会一直等待(阻塞当前线程),直到任务组执行完毕

5.5 dispatch_group_notify
待任务组执行完毕时调用,不会阻塞当前线程

void dispatch_group_notify(dispatch_group_t group,
                           dispatch_queue_t queue, 
                           dispatch_block_t block);
  • group ——需要监听的任务组
  • queue ——block任务执行的线程队列,和之前group执行的线程队列无关
  • block ——任务组执行完毕时需要执行的任务block

示例

- (void)group{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"group one start");
    NSLog(@"CurrentThread --- %@", [NSThread currentThread]);
    dispatch_group_async(group, queue, ^{
        dispatch_async(queue, ^{
            NSLog(@"dispatch_async --- %@", [NSThread currentThread]);
            sleep(3);  //模拟异步请求
            NSLog(@"group one finished");
        });
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"dispatch_group_notify --- %@", [NSThread currentThread]);
        NSLog(@"group finished");
    });
}

2018-05-17 09:32:09.284063+0800 MultiThread[10977:949950] group one start
2018-05-17 09:32:09.284450+0800 MultiThread[10977:949950] CurrentThread --- {number = 1, name = main}
2018-05-17 09:32:09.284987+0800 MultiThread[10977:950236] dispatch_async --- {number = 5, name = (null)}
2018-05-17 09:32:09.285092+0800 MultiThread[10977:950242] dispatch_group_notify --- {number = 6, name = (null)}
2018-05-17 09:32:09.286039+0800 MultiThread[10977:950242] group finished
2018-05-17 09:32:12.291271+0800 MultiThread[10977:950236] group one finished

可以看出来,在group中嵌套了一个异步任务是,开启了一个新线程,新线程是直接返回,group就认为任务完成了。解决上述问题,就需要用了另外两个方法。

- (void)group{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"group one start");
    dispatch_group_enter(group);
    NSLog(@"CurrentThread --- %@", [NSThread currentThread]);
    dispatch_group_async(group, queue, ^{
        dispatch_async(queue, ^{
            NSLog(@"dispatch_async --- %@", [NSThread currentThread]);
            sleep(3);  //模拟异步请求
            NSLog(@"group one finished");
            dispatch_group_leave(group);
        });
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"dispatch_group_notify --- %@", [NSThread currentThread]);
        NSLog(@"group finished");
    });
}

2018-05-17 09:36:48.794270+0800 MultiThread[11054:959258] group one start
2018-05-17 09:36:48.794480+0800 MultiThread[11054:959258] CurrentThread --- {number = 1, name = main}
2018-05-17 09:36:48.794824+0800 MultiThread[11054:959669] dispatch_async --- {number = 5, name = (null)}
2018-05-17 09:36:51.798630+0800 MultiThread[11054:959669] group one finished
2018-05-17 09:36:51.798986+0800 MultiThread[11054:959669] dispatch_group_notify --- {number = 5, name = (null)}
2018-05-17 09:36:51.799302+0800 MultiThread[11054:959669] group finished

刚开始,未完成任务数量+1,等到异步任务完成后,数量-1,然后通知group,任务完成了,就会回调dispatch_group_notify的block内容。

6. GCD信号量(dispatch_semaphore)

dispatch_semaphore是GCD中的信号量,可以处理多线程中线程并发的问题,也可以用作同步处理。

创建信号量,里面的参数是表示信号的总量,值必须>=0

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

发送一个信号,信号量的总数会+1

dispatch_semaphore_signal(semaphore);

信号等待,当信号量的总数<=0的时候,会一直等待,直到信号量的总数>0的时候才会继续下面的执行

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

注意:dispatch_semaphore_wait
当信号量的总数<=0时候,该函数所在的线程就会等待,而信号量的总数>0的时候,该函数就会继续往下执行,同时信号量的总数-1
这里有个等待时间的参数,如果在等待的时间内获得了信号量,那么函数继续往下执行,如果等待时间内信号量一直为0,那么函数也会继续往下执行了

/**
 线程同步
 */
- (void)semaphoreDemoOne{
    NSLog(@"currentThread --- %@", [NSThread currentThread]);
    NSLog(@"semaphore --- begin");
    
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    __block int number = 0;
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"1---%@", [NSThread currentThread]);
        
        number = 100;
        
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"samaphore --- end, number = %d", number);
}

2018-05-17 10:35:31.468369+0800 MultiThread[11654:1048990] currentThread --- {number = 1, name = main}
2018-05-17 10:35:31.468523+0800 MultiThread[11654:1048990] semaphore --- begin
2018-05-17 10:35:33.472110+0800 MultiThread[11654:1049057] 1---{number = 5, name = (null)}
2018-05-17 10:35:33.472321+0800 MultiThread[11654:1048990] samaphore --- end, number = 100

如果注释dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);这句代码,number 就等于0了。block块异步执行添加到了全局并发队列里,所以程序在主线程会跳过block块(同时开辟子线程异步执行block块),执行块外的代码dispatch_semaphore_wait,因为semaphore信号量为0,且时间为DISPATCH_TIME_FOREVER,所以会阻塞当前线程(主线程),进而只执行子线程的block块,直到执行块内部的dispatch_semaphore_signal使得信号量+1。正在被阻塞的线程(主线程)会恢复继续执行。这样保证了线程之间的同步。

售票加锁示例

/**
 模拟两个窗口售票
 */
- (void)initTickets{
    
    self.semaphoreTicket = dispatch_semaphore_create(1);
    self.ticketCount = 10;
    
    dispatch_queue_t queueOne = dispatch_queue_create("com.easyFly.one", 0);
    dispatch_queue_t queueTwo = dispatch_queue_create("com.easyFly.Two", 0);
    
    __weak typeof(self) weakSelf = self;
    
    dispatch_async(queueOne, ^{
        [weakSelf saleTicket];
    });

    dispatch_async(queueTwo, ^{
        [weakSelf saleTicket];
    });
    
}

- (void)saleTicket{
    while (true) {
//        dispatch_semaphore_wait(_semaphoreTicket, DISPATCH_TIME_FOREVER);
        if (self.ticketCount > 0) {
            self.ticketCount --;
            NSLog(@"剩余票 --- %ld   窗口 --- %@", self.ticketCount, [NSThread currentThread]);
        }else{
            NSLog(@"所有票均已售完");
            // 相当于解锁
//            dispatch_semaphore_signal(_semaphoreTicket);
            break;
        }
//        dispatch_semaphore_signal(_semaphoreTicket);
    }
}

2018-05-17 10:58:17.676200+0800 MultiThread[12006:1085650] 剩余票 --- 9 窗口 --- {number = 6, name = (null)}
2018-05-17 10:58:17.676247+0800 MultiThread[12006:1085651] 剩余票 --- 9 窗口 --- {number = 5, name = (null)}
2018-05-17 10:58:17.676370+0800 MultiThread[12006:1085651] 剩余票 --- 8 窗口 --- {number = 5, name = (null)}
2018-05-17 10:58:17.676370+0800 MultiThread[12006:1085650] 剩余票 --- 8 窗口 --- {number = 6, name = (null)}
2018-05-17 10:58:17.676578+0800 MultiThread[12006:1085651] 剩余票 --- 7 窗口 --- {number = 5, name = (null)}
2018-05-17 10:58:17.676641+0800 MultiThread[12006:1085650] 剩余票 --- 6 窗口 --- {number = 6, name = (null)}
2018-05-17 10:58:17.676925+0800 MultiThread[12006:1085650] 剩余票 --- 4 窗口 --- {number = 6, name = (null)}
2018-05-17 10:58:17.676751+0800 MultiThread[12006:1085651] 剩余票 --- 5 窗口 --- {number = 5, name = (null)}
2018-05-17 10:58:17.677039+0800 MultiThread[12006:1085650] 剩余票 --- 3 窗口 --- {number = 6, name = (null)}
2018-05-17 10:58:17.677602+0800 MultiThread[12006:1085651] 剩余票 --- 2 窗口 --- {number = 5, name = (null)}
2018-05-17 10:58:17.677828+0800 MultiThread[12006:1085650] 剩余票 --- 1 窗口 --- {number = 6, name = (null)}
2018-05-17 10:58:17.678013+0800 MultiThread[12006:1085651] 剩余票 --- 0 窗口 --- {number = 5, name = (null)}
2018-05-17 10:58:17.678449+0800 MultiThread[12006:1085650] 所有票均已售完
2018-05-17 10:58:17.678646+0800 MultiThread[12006:1085651] 所有票均已售完

如果注释semaphore相关代码,就会出现一张票卖了两次的情况。打开注释后

2018-05-17 11:00:22.375800+0800 MultiThread[12051:1089200] 剩余票 --- 9 窗口 --- {number = 5, name = (null)}
2018-05-17 11:00:22.376091+0800 MultiThread[12051:1089202] 剩余票 --- 8 窗口 --- {number = 6, name = (null)}
2018-05-17 11:00:22.376561+0800 MultiThread[12051:1089200] 剩余票 --- 7 窗口 --- {number = 5, name = (null)}
2018-05-17 11:00:22.376950+0800 MultiThread[12051:1089202] 剩余票 --- 6 窗口 --- {number = 6, name = (null)}
2018-05-17 11:00:22.377360+0800 MultiThread[12051:1089200] 剩余票 --- 5 窗口 --- {number = 5, name = (null)}
2018-05-17 11:00:22.377834+0800 MultiThread[12051:1089202] 剩余票 --- 4 窗口 --- {number = 6, name = (null)}
2018-05-17 11:00:22.378057+0800 MultiThread[12051:1089200] 剩余票 --- 3 窗口 --- {number = 5, name = (null)}
2018-05-17 11:00:22.378413+0800 MultiThread[12051:1089202] 剩余票 --- 2 窗口 --- {number = 6, name = (null)}
2018-05-17 11:00:22.378687+0800 MultiThread[12051:1089200] 剩余票 --- 1 窗口 --- {number = 5, name = (null)}
2018-05-17 11:00:22.378899+0800 MultiThread[12051:1089202] 剩余票 --- 0 窗口 --- {number = 6, name = (null)}
2018-05-17 11:00:22.378996+0800 MultiThread[12051:1089200] 所有票均已售完
2018-05-17 11:00:22.379244+0800 MultiThread[12051:1089202] 所有票均已售完

这就正常的售票了。
解析一下,两个队列相当于两个售票窗口,初始化信号量为1,当一个队列进入售票过程,dispatch_semaphore_wait使得信号量-1,信号量为0,并执行下方售票代码,dispatch_semaphore_signal使得信号量+1,相当于解锁。实现了线程安全。

参考资料

iOS多线程:『GCD』详尽总结
iOS GCD之dispatch_semaphore(信号量)
关于iOS多线程,你看我就够了

你可能感兴趣的:(iOS开发多线程--GCD)