iOS开发-多线程GCD

今天来看一下iOS中的多线程开发,iOS的多线程开发也有很多种方式,不得不提的有一个就是GCD了。在介绍他之前,先放上苹果官方的GCD源码

GCD是什么

GCD全称Grand Central Dispatch,是Apple开发的一个异步执行任务的技术之一,GCD将iOS系统中很复杂的多线程开发交给系统来做,开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中就可以实现复杂的多线程开发了。

多线程编程

介绍GCD的使用之前,我们先来复习一下多线程编程中的一些概念。

进程

进程是程序在计算机上一次执行的活动,在iOS中,一般来说,一个App就是一个进程,进程可以向系统要求一块内存空间,一个进程可以包含多个线程。

线程

线程是一个独立执行代码段,一个线程同时间内只能执行一个任务,所以多线程并发就可以在同一时间执行多个任务。

主线程

在iOS中,主线程主要的任务是处理UI事件,所以又叫做UI线程,只有主线程才有负责修改UI的能力,而耗时的操作(加载网络资源、上传文件)一般都放在子线程中,但是开辟子线程会占用一定系统资源,所以一般不要同时开特别多线程。

任务

任务一般指的是需要执行的操作,也就是说线程中执行的那段代码。

同步

同步(sync)一般是指当任务添加到执行任务的队列中时,如果队列中有其他先进入的任务,就会一直等待,直到之前的任务都执行完成后才执行。

同步执行任务只能在当前线程中执行任务,不具备开启新线程的能力。

异步

异步(async)一般是指当任务添加到执行任务的队列中时,不做任何的等待,直接执行任务。

异步执行任务可以在新的线程中执行任务,具备开启新线程的能力。(但是是否开启新线程跟任务所指定的队列有关)

队列

这里的队列指的是执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用FIFO(先进先出)的原则,新的任务总是被添加到队尾,读取任务总是从队头读取。

串行

串行队列中每次只会有一个任务被执行。只开启一个线程,一个任务执行完毕后,才执行下一个任务。

并发

并发队列可以让多个任务同时执行。并发队列只有在异步时才会有效。

GCD的使用

苹果的官方说明中说过:

开发者只需要定义想执行的任务并追加到适当的 Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。

也就是说GCD的使用只需要两个步骤:

  • 1.找到适当的等待队列。

  • 2.将任务追加到等待队列中。

找到适当的等待队列

找打适当的等待队列有两种方法,一种是通过dispatch_queue_create方法生成Dispatch Queue,还有一种是获取系统提供的Dispatch Queue。

dispatch_queue_create

使用dispatch_queue_create函数创建队列,需要传入两个参数,第一个作为队列的唯一标示符,可以为空,第二个参数用来识别队列为串行队列还是并发队列。

  • DISPATCH_QUEUE_SERIAL 串行队列

  • DISPATCH_QUEUE_CONCURRENT 并发队列


/*

 创建串行队列

 */

- (void)createSerialQueue

{

 dispatch_queue_t serialQueue = dispatch_queue_create("cn.zhangzr.gcdDemo", DISPATCH_QUEUE_SERIAL);

}


/*

 创建并发队列

 */

- (void)createConcurrentQueue

{

 dispatch_queue_t concurrentQueue = dispatch_queue_create("cn.zhangzr.gcdDemo", DISPATCH_QUEUE_CONCURRENT);

}

Main Dispatch Queue (主队列)

实际上我们可以不用可以创建队列,系统也会给我们一共一些队列,Main Dispatch Queue就是其中一个,看名字就能看的出,一个Main出卖了他是在主线程中执行的身份,因为主线程只会有一个,所以Main Dispatch Queue就是串行队列。

获取他可以通过使用dispatch_get_main_queue()方法获取。


/*

 获取主队列

 */

- (void)getMainQueue

{

 dispatch_queue_t mainQueue = dispatch_get_main_queue();

}

Global Dispatch Queue(全局并发队列)

有了系统提供的串行队列,我们再看看另一个由系统提供的并发队列 Global Dispatch Queue,全局并发队列,它是所有应用程序都能够使用的并发队列。

获取他可以通过使用dispatch_get_global_queue()方法获取,需要提供两个参数,第一个参数为优先级,分为:

  • DISPATCH_QUEUE_PRIORITY_HIGH 最高优先级

  • DISPATCH_QUEUE_PRIORITY_DEFAULT 默认优先级

  • DISPATCH_QUEUE_PRIORITY_LOW 低优先级

  • DISPATCH_QUEUE_PRIORITY_BACKGROUND 后台优先级

第二个参数暂时没用,传入0即可。


/*

 获取全局队列

 */

- (void)getGlobalQueue

{

 dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

}

将任务追加到等待队列中

GCD提供了同步执行任务的创建方法:


 //同步执行

 dispatch_sync(queue, ^{

 do something...

 });

和异步执行任务的创建方法:


 //异步执行

 dispatch_async(queue, ^{

 do something...

 });

GCD的使用实例

通过上边的介绍我们大概知道了有两种队列和两种任务的执行方式,排列组合一下就有如下4中结果

  • 1.同步执行 + 并发队列

  • 2.异步执行 + 并发队列

  • 3.同步执行 + 串行队列

  • 4.异步执行 + 串行队列

另外我们还介绍了两种系统提供的队列的获取方式,全局并发队列可以作为一种普通的并发队列考虑,但是主队列这里有一些特殊,所以我们单独来讨论他。那么就再加上两种组合。

  • 5.同步执行 + 主队列

  • 6.异步执行 + 主队列

那么接下来我们就一一来看一下这些组合的实现

同步执行 + 并发队列

我们先来执行如下代码看看结果:


- (void)syncConcurrent

{

 NSLog(@"current thread---:%@",[NSThread currentThread]); //打印当前线程

 NSLog(@"syncConcurrent---begin");

 dispatch_queue_t queue = dispatch_queue_create("syncConcurrent", DISPATCH_QUEUE_CONCURRENT);

 dispatch_sync(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"1---:%@",[NSThread currentThread]);  //打印当前线程

 }

 });

 dispatch_sync(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"2---:%@",[NSThread currentThread]);  //打印当前线程

 }

 });

 dispatch_sync(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"3---:%@",[NSThread currentThread]);  //打印当前线程

 }

 });

 NSLog(@"syncConcurrent---end");

}

打印结果如下:


2018-03-22 20:22:07.869051+0800 GCDDemo[14040:14475252] current thread---:<NSThread: 0x604000071a80>{number = 1, name = main}

2018-03-22 20:22:07.869227+0800 GCDDemo[14040:14475252] syncConcurrent---begin

2018-03-22 20:22:09.870422+0800 GCDDemo[14040:14475252] 1---:<NSThread: 0x604000071a80>{number = 1, name = main}

2018-03-22 20:22:11.870946+0800 GCDDemo[14040:14475252] 1---:<NSThread: 0x604000071a80>{number = 1, name = main}

2018-03-22 20:22:13.871345+0800 GCDDemo[14040:14475252] 2---:<NSThread: 0x604000071a80>{number = 1, name = main}

2018-03-22 20:22:15.872180+0800 GCDDemo[14040:14475252] 2---:<NSThread: 0x604000071a80>{number = 1, name = main}

2018-03-22 20:22:17.873334+0800 GCDDemo[14040:14475252] 3---:<NSThread: 0x604000071a80>{number = 1, name = main}

2018-03-22 20:22:19.873793+0800 GCDDemo[14040:14475252] 3---:<NSThread: 0x604000071a80>{number = 1, name = main}

2018-03-22 20:22:19.874091+0800 GCDDemo[14040:14475252] syncConcurrent---end

我们可以看到,所有的任务都是在当前线程中执行的,任务的过程中没有开启新的线程。

另外所有的任务都是在begin和end之间执行的。

尽管并发是不需要等待前一个任务结束就可以开始执行,但是因为同步不具备开启线程的功能,所以也只会有一个线程,所以并发也就不存在了。任务只能一个接一个的执行。

异步执行 + 并发队列

先看示例代码:


- (void)asyncConcurrent

{

 NSLog(@"current thread---:%@",[NSThread currentThread]); //打印当前线程

 NSLog(@"asyncConcurrent---begin");

 dispatch_queue_t queue = dispatch_queue_create("asyncConcurrent", DISPATCH_QUEUE_CONCURRENT);

 dispatch_async(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"1---:%@",[NSThread currentThread]);  //打印当前线程

 }

 });

 dispatch_async(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"2---:%@",[NSThread currentThread]);  //打印当前线程

 }

 });

 dispatch_async(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"3---:%@",[NSThread currentThread]);  //打印当前线程

 }

 });

 NSLog(@"asyncConcurrent---end");

}

再看打印结果


2018-03-22 20:26:46.828393+0800 GCDDemo[14132:14488888] current thread---:<NSThread: 0x604000075d00>{number = 1, name = main}

2018-03-22 20:26:46.828575+0800 GCDDemo[14132:14488888] asyncConcurrent---begin

2018-03-22 20:26:46.828700+0800 GCDDemo[14132:14488888] asyncConcurrent---end

2018-03-22 20:26:48.834176+0800 GCDDemo[14132:14488952] 3---:<NSThread: 0x604000274700>{number = 3, name = (null)}

2018-03-22 20:26:48.834180+0800 GCDDemo[14132:14488951] 2---:<NSThread: 0x600000277dc0>{number = 4, name = (null)}

2018-03-22 20:26:48.834180+0800 GCDDemo[14132:14488949] 1---:<NSThread: 0x600000461300>{number = 5, name = (null)}

2018-03-22 20:26:50.838816+0800 GCDDemo[14132:14488949] 1---:<NSThread: 0x600000461300>{number = 5, name = (null)}

2018-03-22 20:26:50.838818+0800 GCDDemo[14132:14488952] 3---:<NSThread: 0x604000274700>{number = 3, name = (null)}

2018-03-22 20:26:50.838831+0800 GCDDemo[14132:14488951] 2---:<NSThread: 0x600000277dc0>{number = 4, name = (null)}

可以明显的看到,这次的结果和上次的结果有很大的不同,首先,任务的过程中开启了很多新的线程。

还有,所有的任务都没有在begin和end之间执行,而是在end之后才执行。

说明异步执行是具备开启线程的能力,并且没有等待之前的任务结束,就继续执行任务了。

同步执行 + 串行队列

先看代码:


- (void)syncSerial

{

 NSLog(@"current thread---:%@",[NSThread currentThread]); //打印当前线程

 NSLog(@"syncSerial---begin");

 dispatch_queue_t queue = dispatch_queue_create("syncSerial", DISPATCH_QUEUE_SERIAL);

 dispatch_sync(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"1---:%@",[NSThread currentThread]);  //打印当前线程

 }

 });

 dispatch_sync(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"2---:%@",[NSThread currentThread]);  //打印当前线程

 }

 });

 dispatch_sync(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"3---:%@",[NSThread currentThread]);  //打印当前线程

 }

 });

 NSLog(@"syncSerial---end");

}

再看打印的结果:


2018-03-22 20:32:44.564374+0800 GCDDemo[14261:14507367] current thread---:<NSThread: 0x6040000781c0>{number = 1, name = main}

2018-03-22 20:32:44.564535+0800 GCDDemo[14261:14507367] syncSerial---begin

2018-03-22 20:32:46.566058+0800 GCDDemo[14261:14507367] 1---:<NSThread: 0x6040000781c0>{number = 1, name = main}

2018-03-22 20:32:48.567670+0800 GCDDemo[14261:14507367] 1---:<NSThread: 0x6040000781c0>{number = 1, name = main}

2018-03-22 20:32:50.568163+0800 GCDDemo[14261:14507367] 2---:<NSThread: 0x6040000781c0>{number = 1, name = main}

2018-03-22 20:32:52.569652+0800 GCDDemo[14261:14507367] 2---:<NSThread: 0x6040000781c0>{number = 1, name = main}

2018-03-22 20:32:54.571255+0800 GCDDemo[14261:14507367] 3---:<NSThread: 0x6040000781c0>{number = 1, name = main}

2018-03-22 20:32:56.572867+0800 GCDDemo[14261:14507367] 3---:<NSThread: 0x6040000781c0>{number = 1, name = main}

2018-03-22 20:32:56.573196+0800 GCDDemo[14261:14507367] syncSerial---end

我们可以看到,这次的任务打印结果和第一次异步执行 + 并发队列的结果基本相同,所有的任务都是在主线程执行的,没有开启新的子线程。

并且所有的任务都是在begin和end之间一个一个按顺序执行的。

异步执行 + 串行队列

还是先看代码:


- (void)asyncSerial

{

 NSLog(@"current thread---:%@",[NSThread currentThread]); //打印当前线程

 NSLog(@"asyncSerial---begin");

 dispatch_queue_t queue = dispatch_queue_create("syncSerial", DISPATCH_QUEUE_SERIAL);

 dispatch_async(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"1---:%@",[NSThread currentThread]);  //打印当前线程

 }

 });

 dispatch_async(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"2---:%@",[NSThread currentThread]);  //打印当前线程

 }

 });

 dispatch_async(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"3---:%@",[NSThread currentThread]);  //打印当前线程

 }

 });

 NSLog(@"asyncSerial---end");

}

再看结果


2018-03-22 20:36:30.829895+0800 GCDDemo[14342:14518025] current thread---:<NSThread: 0x60000007fe80>{number = 1, name = main}

2018-03-22 20:36:30.830061+0800 GCDDemo[14342:14518025] asyncSerial---begin

2018-03-22 20:36:30.830210+0800 GCDDemo[14342:14518025] asyncSerial---end

2018-03-22 20:36:32.832667+0800 GCDDemo[14342:14518069] 1---:<NSThread: 0x60400027d100>{number = 3, name = (null)}

2018-03-22 20:36:34.838401+0800 GCDDemo[14342:14518069] 1---:<NSThread: 0x60400027d100>{number = 3, name = (null)}

2018-03-22 20:36:36.844076+0800 GCDDemo[14342:14518069] 2---:<NSThread: 0x60400027d100>{number = 3, name = (null)}

2018-03-22 20:36:38.849738+0800 GCDDemo[14342:14518069] 2---:<NSThread: 0x60400027d100>{number = 3, name = (null)}

2018-03-22 20:36:40.855311+0800 GCDDemo[14342:14518069] 3---:<NSThread: 0x60400027d100>{number = 3, name = (null)}

2018-03-22 20:36:42.855999+0800 GCDDemo[14342:14518069] 3---:<NSThread: 0x60400027d100>{number = 3, name = (null)}

我们可以看到,所有的任务都是在begin和end之后执行的,并且开启了一条新的线程。

但是因为是串行任务,所以所有的任务还是都是按照顺序一个一个执行的。

同步执行 + 主队列

先看代码:


- (void)syncMain

{

 NSLog(@"current thread---:%@",[NSThread currentThread]); //打印当前线程

 NSLog(@"syncMain---begin");

 dispatch_queue_t queue = dispatch_get_main_queue();

 dispatch_sync(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"1---:%@",[NSThread currentThread]);  //打印当前线程

 }

 });

 dispatch_sync(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"2---:%@",[NSThread currentThread]);  //打印当前线程

 }

 });

 dispatch_sync(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"3---:%@",[NSThread currentThread]);  //打印当前线程

 }

 });

 NSLog(@"syncMain---end");

}

再看结果:


2018-03-22 20:39:58.718404+0800 GCDDemo[14415:14527628] current thread---:<NSThread: 0x60400007af80>{number = 1, name = main}

2018-03-22 20:39:58.718631+0800 GCDDemo[14415:14527628] syncMain---begin

(lldb) 

这时候我们会发现,程序走到begin之后就没有再往下进行了,而且还发生了崩溃。

原因就是我们在主线程中执行了syncMain方法,相当于把syncMain放到了主线程的队列中,但是同步执行的方式会等到当前队列中的任务执行完毕,才会接着执行,这时候我们又把1号任务追加到主线程中,1号任务就在等待主线程处理完syncMain的任务,而syncMain需要等待1号任务执行完毕,才能继续进行。

那这时候就出现了死锁的问题,大家互相等待,所以就都没办法执行下去了。

那如果我们不再主线程中调用呢?


 [NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];

再看结果:


2018-03-22 20:48:02.987608+0800 GCDDemo[14560:14544489] current thread---:<NSThread: 0x604000467680>{number = 3, name = (null)}

2018-03-22 20:48:02.987778+0800 GCDDemo[14560:14544489] syncMain---begin

2018-03-22 20:48:04.992144+0800 GCDDemo[14560:14544241] 1---:<NSThread: 0x600000261b00>{number = 1, name = main}

2018-03-22 20:48:06.993744+0800 GCDDemo[14560:14544241] 1---:<NSThread: 0x600000261b00>{number = 1, name = main}

2018-03-22 20:48:08.995656+0800 GCDDemo[14560:14544241] 2---:<NSThread: 0x600000261b00>{number = 1, name = main}

2018-03-22 20:48:10.996423+0800 GCDDemo[14560:14544241] 2---:<NSThread: 0x600000261b00>{number = 1, name = main}

2018-03-22 20:48:12.999316+0800 GCDDemo[14560:14544241] 3---:<NSThread: 0x600000261b00>{number = 1, name = main}

2018-03-22 20:48:15.000457+0800 GCDDemo[14560:14544241] 3---:<NSThread: 0x600000261b00>{number = 1, name = main}

2018-03-22 20:48:15.003178+0800 GCDDemo[14560:14544489] syncMain---end

这时候我们发现,一切又可以很好的运行起来了,而且都是在主线程中一个一个按照顺序执行的。

那为什么这时候不会发生死锁问题呢?

是因为syncMain放到了其他的线程中,而1号任务、2号任务和3号任务都被追加到了主线程中,这三个任务都会在主线程中执行,而syncMain则是在另外一个子线程中执行的,这时候主队列中就没有等待执行的任务,所以就会直接执行1号任务,所以不会发生死锁。

异步执行 + 主线程

接下来我们看一下异步执行 + 主线程的情况:


- (void)asyncMain

{

 NSLog(@"current thread---:%@",[NSThread currentThread]); //打印当前线程

 NSLog(@"asyncMain---begin");

 dispatch_queue_t queue = dispatch_get_main_queue();

 dispatch_async(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"1---:%@",[NSThread currentThread]);  //打印当前线程

 }

 });

 dispatch_async(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"2---:%@",[NSThread currentThread]);  //打印当前线程

 }

 });

 dispatch_async(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"3---:%@",[NSThread currentThread]);  //打印当前线程

 }

 });

 NSLog(@"asyncMain---end");

}

再看打印的结果:


2018-03-22 20:53:36.799541+0800 GCDDemo[14659:14556655] current thread---:<NSThread: 0x600000076d80>{number = 1, name = main}

2018-03-22 20:53:36.799729+0800 GCDDemo[14659:14556655] asyncMain---begin

2018-03-22 20:53:36.799843+0800 GCDDemo[14659:14556655] asyncMain---end

2018-03-22 20:53:38.804826+0800 GCDDemo[14659:14556655] 1---:<NSThread: 0x600000076d80>{number = 1, name = main}

2018-03-22 20:53:40.805504+0800 GCDDemo[14659:14556655] 1---:<NSThread: 0x600000076d80>{number = 1, name = main}

2018-03-22 20:53:42.807095+0800 GCDDemo[14659:14556655] 2---:<NSThread: 0x600000076d80>{number = 1, name = main}

2018-03-22 20:53:44.808701+0800 GCDDemo[14659:14556655] 2---:<NSThread: 0x600000076d80>{number = 1, name = main}

2018-03-22 20:53:46.809607+0800 GCDDemo[14659:14556655] 3---:<NSThread: 0x600000076d80>{number = 1, name = main}

2018-03-22 20:53:48.811117+0800 GCDDemo[14659:14556655] 3---:<NSThread: 0x600000076d80>{number = 1, name = main}

可以看到,所有的任务都会追加到主线程中来执行,但是都会是在end之后,也就是说不会等待之前的任务完成就会继续后边的任务,但是因为所有的任务都被追加到主线程这一条线程中执行,是串行队列,所以任务都是一个接一个的按顺序执行的。

总结

6种组合方法都已经使用过了,接下来让我们来列个表格总结一下吧。

| | 并发队列 | 串行队列 | 主队列 |

| --- | --- | --- | --- |

| 同步 | 不开启新线程,串行执行任务 | 不开启新线程,串行执行任务 | 主线程调用:发生死锁,卡死。其他线程调用:不开启新线程,串行执行任务 |

| 异步 | 开启新线程,并发执行任务 | 开启一条新线程,串行执行任务 | 不开启新线程,串行执行任务|

GCD 的其他API

除了刚才介绍过的dispatch_queue_create()dispatch_sync()dispatch_async()...之外,GCD的作用还有很多,接下来我们就来看看GCD的其他方法。

dispatch_set_target_queue

这个函数主要有两个作用:

  • 1.改变队列的优先级;

  • 2.防止多个串行队列并发执行;

改变队列的优先级

使用 dispatch_queue_create函数生成的队列,无论是串行的还是并发的,都是与默认优先级Global Dispatch Queue一致。

如果想要改变队列的优先级,就需要使用到dispatch_set_target_queue。


 dispatch_set_target_queue(dispatch_object_t _Nonnull object, dispatch_queue_t _Nullable queue)

第一个参数:需要改变优先级的队列;

第二个参数:指定要执行的目标队列;

举个例子:

生成一个后台的串行队列


- (void)changeQueue

{

 dispatch_queue_t serialQueue = dispatch_queue_create("com.example.gcd", NULL);

 dispatch_queue_t globalBackgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

 //修改优先级为后台串行队列

 dispatch_set_target_queue(serialQueue, globalBackgroundQueue);

}

防止多个串行队列并发执行

我们还可以使用dispatch_set_target_queue函数将目标函数指定为某个串行队列(Serial Dispatch Queue),就可以防止这些处理并发执行。

举个例子:


- (void)changeQueue2

{

 NSMutableArray *queueArr = [NSMutableArray array];

 for(NSInteger i = 0 ; i < 5 ; i++)

 {

 dispatch_queue_t serial_queue = dispatch_queue_create("com.example.gcd.changeQueue2", NULL);

 [queueArr addObject:serial_queue];

 }

 [queueArr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {

 dispatch_queue_t serial_queue = obj;

 dispatch_async(serial_queue, ^{

 NSLog(@"执行任务%ld",idx);

 });

 }];

}

输出:


2018-03-26 16:27:10.890910+0800 GCDDemo[1501:115539] 执行任务1

2018-03-26 16:27:10.890910+0800 GCDDemo[1501:115541] 执行任务0

2018-03-26 16:27:10.890910+0800 GCDDemo[1501:115540] 执行任务2

2018-03-26 16:27:10.890968+0800 GCDDemo[1501:115542] 执行任务3

2018-03-26 16:27:10.891012+0800 GCDDemo[1501:115561] 执行任务4

这段代码运行了之后我们会发现,串行队列没有按照顺序一个一个执行。

接下来,我们添加一个目标队列之后


- (void)changeQueue2

{

 NSMutableArray *queueArr = [NSMutableArray array];

 //创建一个目标队列

 dispatch_queue_t serial_target_queue = dispatch_queue_create("com.example.gcd.changeQueueTarget", NULL);

 for(NSInteger i = 0 ; i < 5 ; i++)

 {

 dispatch_queue_t serial_queue = dispatch_queue_create("com.example.gcd.changeQueue2", NULL);

 //添加目标队列

 dispatch_set_target_queue(serial_queue, serial_target_queue);

 [queueArr addObject:serial_queue];

 }

 [queueArr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {

 dispatch_queue_t serial_queue = obj;

 dispatch_async(serial_queue, ^{

 NSLog(@"执行任务%ld",idx);

 });

 }];

}

可以看到任务就按照我们的添加顺序执行了。


2018-03-26 16:30:29.610249+0800 GCDDemo[1566:120853] 执行任务0

2018-03-26 16:30:29.610430+0800 GCDDemo[1566:120853] 执行任务1

2018-03-26 16:30:29.610565+0800 GCDDemo[1566:120853] 执行任务2

2018-03-26 16:30:29.610678+0800 GCDDemo[1566:120853] 执行任务3

2018-03-26 16:30:29.611625+0800 GCDDemo[1566:120853] 执行任务4

dispatch_after

这个函数可以用来延迟执行block中的代码。


 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

 });

一般情况下,我们只需要修改delayInSeconds就可以了。

举个例子:

比如我想要3秒之后执行某个事情


- (void)afterRun

{

 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

 //写你要做的事情

 });

}

但是这里需要注意一点,这里的时间不是绝对的3秒后,而是在3秒后追加到Dispatch Queue中,效果和在3秒后使用dispatch_async追加到Main Queue效果相同。

因为Main Dispatch Queue是在主线程的RunLoop中执行的,所以在比如每个1/60秒执行的RunLoop中,block最快在3秒后执行,最慢在3+1/60秒后执行。但是如果主线程本身有延迟的话,这个时间就会更长一些了。

dispatch_once

这个函数可以用来保证在应用程序执行中只执行一次操作。一般在创建单例的时候经常使用,即使在多线程的情况下,也能保证线程的安全。


- (void)runOnce

{

 static dispatch_once_t onceToken;

 dispatch_once(&onceToken, ^{

 //写你要做的事情

 });

}

dispatch_group

这个函数一般用来实现这样的需求,有三个耗时任务A、B、C,其中C需要等A和B异步执行完之后才能执行。这个时候我们就可以用到队列组来实现。

队列组中有三种方法能够实现这样的需求。

  • 1.使用 dispatch_group_notify

  • 2.使用 dispatch_group_wait

  • 3.使用 dispatch_group_enter,dispatch_group_leave

dispatch_group_notify

这个方法可以监听group中任务的完成状态,当所有的任务都执行完成之后,追加任务到group中,并执行任务。


- (void)groupNotify

{

 NSLog(@"current thread---:%@",[NSThread currentThread]); //打印当前线程

 NSLog(@"asyncMain---begin");

 dispatch_group_t group = dispatch_group_create();

 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"任务A---:%@",[NSThread currentThread]); //打印当前线程

 }

 });

 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"任务B---:%@",[NSThread currentThread]); //打印当前线程

 }

 });

 dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"任务C---:%@",[NSThread currentThread]); //打印当前线程

 }

 });

 NSLog(@"asyncMain---end");

}

打印结果:


2018-03-26 17:31:06.806883+0800 GCDDemo[2244:190058] current thread---:<NSThread: 0x604000077980>{number = 1, name = main}

2018-03-26 17:31:06.807055+0800 GCDDemo[2244:190058] asyncMain---begin

2018-03-26 17:31:06.807225+0800 GCDDemo[2244:190058] asyncMain---end

2018-03-26 17:31:08.807669+0800 GCDDemo[2244:190117] 任务A---:<NSThread: 0x60400027e280>{number = 3, name = (null)}

2018-03-26 17:31:08.807669+0800 GCDDemo[2244:190114] 任务B---:<NSThread: 0x60400027e2c0>{number = 4, name = (null)}

2018-03-26 17:31:10.813329+0800 GCDDemo[2244:190117] 任务A---:<NSThread: 0x60400027e280>{number = 3, name = (null)}

2018-03-26 17:31:10.813329+0800 GCDDemo[2244:190114] 任务B---:<NSThread: 0x60400027e2c0>{number = 4, name = (null)}

2018-03-26 17:31:12.817688+0800 GCDDemo[2244:190114] 任务C---:<NSThread: 0x60400027e2c0>{number = 4, name = (null)}

2018-03-26 17:31:14.818356+0800 GCDDemo[2244:190114] 任务C---:<NSThread: 0x60400027e2c0>{number = 4, name = (null)}

可以看到,任务A和任务B是异步的,没有按照顺序执行,但是任务C是在A和B都结束之后才开始执行的。

dispatch_group_wait

这个方法可以阻塞当前线程,等待group中的任务全部完成之后,才会继续往下执行。

这里边的第二个参数用来表示阻塞当前线程的时间,如果传入DISPATCH_TIME_FOREVER则表示永远,直到group中的任务执行完毕。


- (void)groupWait

{

 NSLog(@"current thread---:%@",[NSThread currentThread]); //打印当前线程

 NSLog(@"asyncMain---begin");

 dispatch_group_t group = dispatch_group_create();

 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"任务A---:%@",[NSThread currentThread]); //打印当前线程

 }

 });

 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"任务B---:%@",[NSThread currentThread]); //打印当前线程

 }

 });

 dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"任务C---:%@",[NSThread currentThread]); //打印当前线程

 }

 });

 NSLog(@"asyncMain---end");

}

打印结果:


2018-03-26 17:44:11.898008+0800 GCDDemo[2420:207859] current thread---:<NSThread: 0x60400007ebc0>{number = 1, name = main}

2018-03-26 17:44:11.898194+0800 GCDDemo[2420:207859] asyncMain---begin

2018-03-26 17:44:13.903195+0800 GCDDemo[2420:207951] 任务A---:<NSThread: 0x604000478940>{number = 4, name = (null)}

2018-03-26 17:44:13.903213+0800 GCDDemo[2420:207954] 任务B---:<NSThread: 0x604000478740>{number = 3, name = (null)}

2018-03-26 17:44:15.904134+0800 GCDDemo[2420:207951] 任务A---:<NSThread: 0x604000478940>{number = 4, name = (null)}

2018-03-26 17:44:15.904177+0800 GCDDemo[2420:207954] 任务B---:<NSThread: 0x604000478740>{number = 3, name = (null)}

2018-03-26 17:44:15.904431+0800 GCDDemo[2420:207859] asyncMain---end

2018-03-26 17:44:17.909766+0800 GCDDemo[2420:207954] 任务C---:<NSThread: 0x604000478740>{number = 3, name = (null)}

2018-03-26 17:44:19.911876+0800 GCDDemo[2420:207954] 任务C---:<NSThread: 0x604000478740>{number = 3, name = (null)}

dispatch_group_enter + dispatch_group_leave

dispatch_group_enter 表示将一个任务追加到了group中,执行一次,则表示group中未完成的任务+1;

dispatch_group_leave 表示一个任务离开了group中,执行一次,则表示group中未完成的任务-1;

当group中未完成的任务数为0的时候,才会开始执行下边的任务。


- (void)groupEnterLeave

{

 NSLog(@"current thread---:%@",[NSThread currentThread]); //打印当前线程

 NSLog(@"asyncMain---begin");

 dispatch_group_t group = dispatch_group_create();

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

 dispatch_group_enter(group);

 dispatch_async(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"任务A---:%@",[NSThread currentThread]); //打印当前线程

 }

 dispatch_group_leave(group);

 });

 dispatch_group_enter(group);

 dispatch_async(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"任务B---:%@",[NSThread currentThread]); //打印当前线程

 }

 dispatch_group_leave(group);

 });

 dispatch_group_notify(group, dispatch_get_main_queue(), ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"任务C---:%@",[NSThread currentThread]); //打印当前线程

 }

 });

 /**

 或者使用

 dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"任务C---:%@",[NSThread currentThread]); //打印当前线程

 }

 });

 */

 NSLog(@"asyncMain---end");

}

输出结果:


2018-03-26 17:51:20.854245+0800 GCDDemo[2536:218929] current thread---:<NSThread: 0x6000000646c0>{number = 1, name = main}

2018-03-26 17:51:20.854413+0800 GCDDemo[2536:218929] asyncMain---begin

2018-03-26 17:51:20.854603+0800 GCDDemo[2536:218929] asyncMain---end

2018-03-26 17:51:22.859505+0800 GCDDemo[2536:219069] 任务A---:<NSThread: 0x60000007a5c0>{number = 4, name = (null)}

2018-03-26 17:51:22.859505+0800 GCDDemo[2536:219072] 任务B---:<NSThread: 0x60400026bf00>{number = 3, name = (null)}

2018-03-26 17:51:24.862088+0800 GCDDemo[2536:219072] 任务B---:<NSThread: 0x60400026bf00>{number = 3, name = (null)}

2018-03-26 17:51:24.862091+0800 GCDDemo[2536:219069] 任务A---:<NSThread: 0x60000007a5c0>{number = 4, name = (null)}

2018-03-26 17:51:26.863621+0800 GCDDemo[2536:218929] 任务C---:<NSThread: 0x6000000646c0>{number = 1, name = main}

2018-03-26 17:51:28.864385+0800 GCDDemo[2536:218929] 任务C---:<NSThread: 0x6000000646c0>{number = 1, name = main}

dispatch_barrier_async

这个方法有一个很接地气的方法,叫做栅栏方法,听名字大概就能明白意思了,这个方法就是为了把队列中的任务分割开的。

当我们在异步执行的时候,有两组操作,并且只有当第一组操作全部执行完之后再执行第二组操作时就可以用到这个方法。


- (void)barrierAsync

{

 dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.changeQueueTarget", DISPATCH_QUEUE_CONCURRENT);

 dispatch_async(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"任务A---:%@",[NSThread currentThread]); //打印当前线程

 }

 });

 dispatch_async(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"任务B---:%@",[NSThread currentThread]); //打印当前线程

 }

 });

 dispatch_barrier_async(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"任务C---:%@",[NSThread currentThread]); //打印当前线程

 }

 });

 dispatch_async(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"任务D---:%@",[NSThread currentThread]); //打印当前线程

 }

 });

 dispatch_async(queue, ^{

 for(int i = 0 ; i < 2 ; i++)

 {

 [NSThread sleepForTimeInterval:2]; //模拟耗时操作

 NSLog(@"任务E---:%@",[NSThread currentThread]); //打印当前线程

 }

 });

}

输出结果


2018-03-26 21:24:29.637512+0800 GCDDemo[3423:291454] 任务A---:<NSThread: 0x604000269440>{number = 4, name = (null)}

2018-03-26 21:24:29.637512+0800 GCDDemo[3423:291455] 任务B---:<NSThread: 0x600000465180>{number = 3, name = (null)}

2018-03-26 21:24:31.639796+0800 GCDDemo[3423:291454] 任务A---:<NSThread: 0x604000269440>{number = 4, name = (null)}

2018-03-26 21:24:31.639859+0800 GCDDemo[3423:291455] 任务B---:<NSThread: 0x600000465180>{number = 3, name = (null)}

2018-03-26 21:24:33.641645+0800 GCDDemo[3423:291455] 任务C---:<NSThread: 0x600000465180>{number = 3, name = (null)}

2018-03-26 21:24:35.646223+0800 GCDDemo[3423:291455] 任务C---:<NSThread: 0x600000465180>{number = 3, name = (null)}

2018-03-26 21:24:37.650334+0800 GCDDemo[3423:291455] 任务D---:<NSThread: 0x600000465180>{number = 3, name = (null)}

2018-03-26 21:24:37.650404+0800 GCDDemo[3423:291454] 任务E---:<NSThread: 0x604000269440>{number = 4, name = (null)}

2018-03-26 21:24:39.653433+0800 GCDDemo[3423:291455] 任务D---:<NSThread: 0x600000465180>{number = 3, name = (null)}

2018-03-26 21:24:39.653444+0800 GCDDemo[3423:291454] 任务E---:<NSThread: 0x604000269440>{number = 4, name = (null)}

dispatch_apply

通过这个函数,我们可以按照指定次数,将block追加到制定队列中,并等待任务全部执行完之后执行后边的代码。


- (void)dispatchApply1

{

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

 dispatch_apply(10, queue, ^(size_t index) {

 NSLog(@"%ld",index);

 });

}

输出:


2018-03-26 21:31:18.887232+0800 GCDDemo[3529:309734] 0

2018-03-26 21:31:18.887237+0800 GCDDemo[3529:309775] 3

2018-03-26 21:31:18.887232+0800 GCDDemo[3529:309777] 1

2018-03-26 21:31:18.887237+0800 GCDDemo[3529:309776] 2

2018-03-26 21:31:18.887407+0800 GCDDemo[3529:309775] 4

2018-03-26 21:31:18.887407+0800 GCDDemo[3529:309734] 5

2018-03-26 21:31:18.887411+0800 GCDDemo[3529:309777] 6

2018-03-26 21:31:18.887498+0800 GCDDemo[3529:309776] 7

2018-03-26 21:31:18.887513+0800 GCDDemo[3529:309775] 8

2018-03-26 21:31:18.887587+0800 GCDDemo[3529:309734] 9

这个函数可以使用并发队列异步执行所有的任务,所以也可以用来遍历数组中的内容,只是顺序并不会按照数组的下标顺序来


- (void)dispatchApply2

{

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

 NSArray *array = @[@11,@12,@13,@14,@15,@16,@17,@18,@19,@20];

 dispatch_apply([array count], queue, ^(size_t index) {

 NSLog(@"%@",array[index]);

 });

}

输出


2018-03-26 21:36:02.094003+0800 GCDDemo[3638:322810] 14

2018-03-26 21:36:02.094003+0800 GCDDemo[3638:322506] 11

2018-03-26 21:36:02.094003+0800 GCDDemo[3638:322813] 13

2018-03-26 21:36:02.094003+0800 GCDDemo[3638:322812] 12

2018-03-26 21:36:02.094206+0800 GCDDemo[3638:322810] 16

2018-03-26 21:36:02.094206+0800 GCDDemo[3638:322812] 15

2018-03-26 21:36:02.094208+0800 GCDDemo[3638:322506] 18

2018-03-26 21:36:02.094208+0800 GCDDemo[3638:322813] 17

2018-03-26 21:36:02.094292+0800 GCDDemo[3638:322810] 19

2018-03-26 21:36:02.094313+0800 GCDDemo[3638:322812] 20

dispatch_semaphore

dispatch semaphore 称为GCD中的信号量,是持有计数的信号,当计数为0的时候等待,不可通过,当计数大于等于1时,计数减一且不等待,可通过。

dispatch_semaphore提供了三个函数:

  • dispatch_semaphore_create 创建一个Semphore并初始化信号的总量。

  • dispatch_semaphore_signal 发送一个信号,让信号总量加1。

  • dispatch_semaphore_wait 可以使总信号量减1,让信号总量为0时就会一直等待,否则就可以正常执行。

dispatch semaphore在开发中主要用于

  • 保持线程同步,将异步执行任务转换为同步执行任务

  • 保证线程安全,为线程加锁

保持线程同步

使用dispatch semphore,可以使异步执行的任务转换成同步执行的任务,比如下边代码中,我们添加了一个异步执行的方法:


- (void)semaphoreSync

{

 NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程

 NSLog(@"semaphore---begin");

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

 __block int number = 0;

 //添加异步任务

 dispatch_async(queue, ^{

 // 追加任务1

 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作

 NSLog(@"%@",[NSThread currentThread]); // 打印当前线程

 number = 100;

 dispatch_semaphore_signal(semaphore);

 });

 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

 NSLog(@"semaphore---end,number = %zd",number);

}

输出结果:


2018-03-26 21:51:24.444082+0800 GCDDemo[3792:354114] currentThread---<NSThread: 0x60000006ad00>{number = 1, name = main}

2018-03-26 21:51:24.444267+0800 GCDDemo[3792:354114] semaphore---begin

2018-03-26 21:51:26.449933+0800 GCDDemo[3792:354277] <NSThread: 0x60000027d540>{number = 3, name = (null)}

2018-03-26 21:51:26.450298+0800 GCDDemo[3792:354114] semaphore---end,number = 100

但是看到结果,end方法是在最后才执行的,semaphore将异步任务转换成了同步任务。

保证线程安全

如果我们的应用中,有一个变量,每个线程都可以对他进行读写,那如果多个线程对他同时进行读写,就会影响到线程安全。

举个例子,有两个线程,线程A和线程B,他们同时都要对某个变量进行+1操作,如果此时变量值为10,线程A执行读取操作时,线程B也执行了读取操作,他们又都进行了+1操作,这时候实际上是执行了两次+1,但是,他们将变量写回去时却只加了一次,这就影响了线程安全。

下边用一个火车票售卖的方式来看看线程安全的问题。

比如,一共有50张火车票,共有AB两个售票口,他们同时卖票,直到卖完为止。

非线程安全时:


- (void)semaphoreNotSafe

{

 self.ticketNumer = 50;

 dispatch_queue_t queueA = dispatch_queue_create("com.example.gcd.notSafeA", DISPATCH_QUEUE_SERIAL);

 dispatch_queue_t queueB = dispatch_queue_create("com.example.gcd.notSafeB", DISPATCH_QUEUE_SERIAL);

 __weak ViewController *weakSelf = self;

 dispatch_async(queueA, ^{

 [weakSelf saleTicket];

 });

 dispatch_async(queueB, ^{

 [weakSelf saleTicket];

 });

}

- (void)saleTicket

{

 while (1) {

 if(self.ticketNumer > 0)

 {

 self.ticketNumer--;

 NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketNumer, [NSThread currentThread]]);

 [NSThread sleepForTimeInterval:1];

 }

 else

 {

 NSLog(@"票已卖完");

 break;

 }

 }

}

可以看到打印结果:


2018-03-26 22:04:59.329860+0800 GCDDemo[4000:383727] 剩余票数:49 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:04:59.329860+0800 GCDDemo[4000:383728] 剩余票数:48 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:00.334582+0800 GCDDemo[4000:383727] 剩余票数:47 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:00.334571+0800 GCDDemo[4000:383728] 剩余票数:47 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:01.335954+0800 GCDDemo[4000:383728] 剩余票数:46 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:01.335973+0800 GCDDemo[4000:383727] 剩余票数:45 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:02.338000+0800 GCDDemo[4000:383728] 剩余票数:43 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:02.338004+0800 GCDDemo[4000:383727] 剩余票数:44 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:03.340447+0800 GCDDemo[4000:383728] 剩余票数:42 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:03.340447+0800 GCDDemo[4000:383727] 剩余票数:42 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:04.341005+0800 GCDDemo[4000:383727] 剩余票数:40 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:04.341005+0800 GCDDemo[4000:383728] 剩余票数:41 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:05.345193+0800 GCDDemo[4000:383728] 剩余票数:39 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:05.345193+0800 GCDDemo[4000:383727] 剩余票数:38 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:06.345793+0800 GCDDemo[4000:383727] 剩余票数:37 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:06.345793+0800 GCDDemo[4000:383728] 剩余票数:36 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:07.352213+0800 GCDDemo[4000:383727] 剩余票数:35 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:07.352213+0800 GCDDemo[4000:383728] 剩余票数:34 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:08.352845+0800 GCDDemo[4000:383727] 剩余票数:33 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:08.352848+0800 GCDDemo[4000:383728] 剩余票数:32 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:09.353498+0800 GCDDemo[4000:383727] 剩余票数:30 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:09.353502+0800 GCDDemo[4000:383728] 剩余票数:31 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:10.359020+0800 GCDDemo[4000:383727] 剩余票数:29 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:10.359020+0800 GCDDemo[4000:383728] 剩余票数:28 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:11.364586+0800 GCDDemo[4000:383728] 剩余票数:26 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:11.364592+0800 GCDDemo[4000:383727] 剩余票数:27 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:12.369457+0800 GCDDemo[4000:383727] 剩余票数:24 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:12.369457+0800 GCDDemo[4000:383728] 剩余票数:25 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:13.370120+0800 GCDDemo[4000:383728] 剩余票数:22 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:13.370120+0800 GCDDemo[4000:383727] 剩余票数:23 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:14.372062+0800 GCDDemo[4000:383728] 剩余票数:21 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:14.372069+0800 GCDDemo[4000:383727] 剩余票数:21 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:15.372638+0800 GCDDemo[4000:383728] 剩余票数:20 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:15.373013+0800 GCDDemo[4000:383727] 剩余票数:19 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:16.373716+0800 GCDDemo[4000:383727] 剩余票数:18 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:16.373730+0800 GCDDemo[4000:383728] 剩余票数:18 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:17.377931+0800 GCDDemo[4000:383728] 剩余票数:17 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:17.377949+0800 GCDDemo[4000:383727] 剩余票数:16 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:18.383555+0800 GCDDemo[4000:383727] 剩余票数:15 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:18.383571+0800 GCDDemo[4000:383728] 剩余票数:14 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:19.386063+0800 GCDDemo[4000:383728] 剩余票数:13 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:19.386063+0800 GCDDemo[4000:383727] 剩余票数:13 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:20.386737+0800 GCDDemo[4000:383728] 剩余票数:12 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:20.386752+0800 GCDDemo[4000:383727] 剩余票数:12 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:21.389788+0800 GCDDemo[4000:383727] 剩余票数:11 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:21.389793+0800 GCDDemo[4000:383728] 剩余票数:10 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:22.394476+0800 GCDDemo[4000:383727] 剩余票数:9 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:22.394476+0800 GCDDemo[4000:383728] 剩余票数:9 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:23.395737+0800 GCDDemo[4000:383728] 剩余票数:8 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:23.395732+0800 GCDDemo[4000:383727] 剩余票数:7 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:24.401277+0800 GCDDemo[4000:383728] 剩余票数:6 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:24.401277+0800 GCDDemo[4000:383727] 剩余票数:5 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:25.402654+0800 GCDDemo[4000:383728] 剩余票数:4 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:25.402654+0800 GCDDemo[4000:383727] 剩余票数:3 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:26.406315+0800 GCDDemo[4000:383728] 剩余票数:1 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:26.406307+0800 GCDDemo[4000:383727] 剩余票数:2 窗口:<NSThread: 0x60400007ab00>{number = 3, name = (null)}

2018-03-26 22:05:27.407101+0800 GCDDemo[4000:383728] 剩余票数:0 窗口:<NSThread: 0x60400007adc0>{number = 4, name = (null)}

2018-03-26 22:05:27.407102+0800 GCDDemo[4000:383727] 票已卖完

2018-03-26 22:05:28.409874+0800 GCDDemo[4000:383728] 票已卖完

有很多时候票数是乱的,这样的结果就是线程不安全的情况。

线程安全时:


- (void)semaphoreSafe

{

 self.ticketNumer = 50;

 dispatch_queue_t queueA = dispatch_queue_create("com.example.gcd.notSafeA", DISPATCH_QUEUE_SERIAL);

 dispatch_queue_t queueB = dispatch_queue_create("com.example.gcd.notSafeB", DISPATCH_QUEUE_SERIAL);

 _dispatchSemaphore = dispatch_semaphore_create(1);

 __weak ViewController *weakSelf = self;

 dispatch_async(queueA, ^{

 [weakSelf saleTicket];

 });

 dispatch_async(queueB, ^{

 [weakSelf saleTicket];

 });

}

- (void)saleTicket

{

 while (1) {

 //加锁

 dispatch_semaphore_wait(_dispatchSemaphore, DISPATCH_TIME_FOREVER);

 if(self.ticketNumer > 0)

 {

 self.ticketNumer--;

 NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketNumer, [NSThread currentThread]]);

 [NSThread sleepForTimeInterval:1];

 }

 else

 {

 NSLog(@"票已卖完");

 dispatch_semaphore_signal(_dispatchSemaphore);

 break;

 }

 //解锁

 dispatch_semaphore_signal(_dispatchSemaphore);

 }

}

输出结果:


2018-03-26 22:10:45.761891+0800 GCDDemo[4101:403227] 剩余票数:49 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:10:46.766212+0800 GCDDemo[4101:403228] 剩余票数:48 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:10:47.771028+0800 GCDDemo[4101:403227] 剩余票数:47 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:10:48.771842+0800 GCDDemo[4101:403228] 剩余票数:46 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:10:49.772338+0800 GCDDemo[4101:403227] 剩余票数:45 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:10:50.777860+0800 GCDDemo[4101:403228] 剩余票数:44 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:10:51.782705+0800 GCDDemo[4101:403227] 剩余票数:43 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:10:52.785061+0800 GCDDemo[4101:403228] 剩余票数:42 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:10:53.789741+0800 GCDDemo[4101:403227] 剩余票数:41 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:10:54.795152+0800 GCDDemo[4101:403228] 剩余票数:40 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:10:55.799427+0800 GCDDemo[4101:403227] 剩余票数:39 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:10:56.802008+0800 GCDDemo[4101:403228] 剩余票数:38 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:10:57.806299+0800 GCDDemo[4101:403227] 剩余票数:37 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:10:58.806870+0800 GCDDemo[4101:403228] 剩余票数:36 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:10:59.808545+0800 GCDDemo[4101:403227] 剩余票数:35 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:11:00.809948+0800 GCDDemo[4101:403228] 剩余票数:34 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:11:01.812548+0800 GCDDemo[4101:403227] 剩余票数:33 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:11:02.817154+0800 GCDDemo[4101:403228] 剩余票数:32 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:11:03.819159+0800 GCDDemo[4101:403227] 剩余票数:31 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:11:04.819982+0800 GCDDemo[4101:403228] 剩余票数:30 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:11:05.821800+0800 GCDDemo[4101:403227] 剩余票数:29 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:11:06.825885+0800 GCDDemo[4101:403228] 剩余票数:28 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:11:07.826912+0800 GCDDemo[4101:403227] 剩余票数:27 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:11:08.832396+0800 GCDDemo[4101:403228] 剩余票数:26 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:11:09.835059+0800 GCDDemo[4101:403227] 剩余票数:25 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:11:10.840474+0800 GCDDemo[4101:403228] 剩余票数:24 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:11:11.846021+0800 GCDDemo[4101:403227] 剩余票数:23 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:11:12.851628+0800 GCDDemo[4101:403228] 剩余票数:22 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:11:13.853427+0800 GCDDemo[4101:403227] 剩余票数:21 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:11:14.856674+0800 GCDDemo[4101:403228] 剩余票数:20 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:11:15.861639+0800 GCDDemo[4101:403227] 剩余票数:19 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:11:16.863499+0800 GCDDemo[4101:403228] 剩余票数:18 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:11:17.868928+0800 GCDDemo[4101:403227] 剩余票数:17 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:11:18.869595+0800 GCDDemo[4101:403228] 剩余票数:16 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:11:19.870315+0800 GCDDemo[4101:403227] 剩余票数:15 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:11:20.875770+0800 GCDDemo[4101:403228] 剩余票数:14 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:11:21.881166+0800 GCDDemo[4101:403227] 剩余票数:13 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:11:22.885946+0800 GCDDemo[4101:403228] 剩余票数:12 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:11:23.886687+0800 GCDDemo[4101:403227] 剩余票数:11 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:11:24.887354+0800 GCDDemo[4101:403228] 剩余票数:10 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:11:25.890562+0800 GCDDemo[4101:403227] 剩余票数:9 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:11:26.893624+0800 GCDDemo[4101:403228] 剩余票数:8 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:11:27.899003+0800 GCDDemo[4101:403227] 剩余票数:7 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:11:28.903570+0800 GCDDemo[4101:403228] 剩余票数:6 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:11:29.909605+0800 GCDDemo[4101:403227] 剩余票数:5 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:11:30.915243+0800 GCDDemo[4101:403228] 剩余票数:4 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:11:31.920318+0800 GCDDemo[4101:403227] 剩余票数:3 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:11:32.921032+0800 GCDDemo[4101:403228] 剩余票数:2 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:11:33.926648+0800 GCDDemo[4101:403227] 剩余票数:1 窗口:<NSThread: 0x604000275080>{number = 3, name = (null)}

2018-03-26 22:11:34.932261+0800 GCDDemo[4101:403228] 剩余票数:0 窗口:<NSThread: 0x600000278900>{number = 4, name = (null)}

2018-03-26 22:11:35.937217+0800 GCDDemo[4101:403227] 票已卖完

2018-03-26 22:11:35.937663+0800 GCDDemo[4101:403228] 票已卖完

这时候就可以看到,票的数量都是按照正常的顺序一张一张减少,也没有出现错乱的问题,这时候就是线程安全的了。

最后

以上差不多就是GCD的全部内容了,这里也只是多线程开发的一小部分,写这一部分的大神也有很多,这篇文章很多内容都是参考自他们的博客,还有一部分来自于《Objective-C 高级编程》的第三章,本文章仅限个人学习使用,如果有什么不对的地方还请大佬们多多指教。

啊,对了,最后还是放一个demo地址吧。

参考文档

Objective-C 高级编程 - 第三章 Grand Central Dispatch

iOS中GCD的使用小结

iOS多线程GCD篇

iOS多线程:『GCD』详尽总结

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容