iOS中的多线程-GCD用法

xiaoxiao2021-02-28  9

GCD中涉及的基本名词:

线程:程序执行任务的最小调度单位

任务:需要在主线程或者子线程中执行的代码,在GCD中显示为Block中

队列:一种特殊的线性表,采取FIFO(先进先出),可以看做是用来存储任务的数组

异步:开辟新线程,同时执行多个任务

同步:在单一线程中执行,只能按顺序从前往后执行

并行队列:队列中的任务同时进行,可以开启多个线程,并发功能只有在异步情况有效

串行队列:队列中的任务逐个进行,只有单一线程

 

GCD的创建:

概述的讲,就是创建一个队列,然后将任务添加到队列中去

(1)创建队列

// ?????????串行队列 dispatch_queue_t queue = dispatch_queue_create("com.app", DISPATCH_QUEUE_SERIAL); // ?????????并发队列 dispatch_queue_t queue = dispatch_queue_create("com.app", DISPATCH_QUEUE_CONCURRENT);

第一个参数是队列的标志符,可以为空,第二个参数表示是串行队列还是并发队列

GCD中有简便的方法获取队列,一个是获取主队列(串行队列),还有一个是获取全局并发队列(并发队列)

//主队列 dispatch_queue_t queue = dispatch_get_main_queue(); //全局并发队列,第一个参数表示优先级,第二个参数是保留参数,暂时无用 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DE FAULT, 0);

(2)创建任务

dispatch_sync(queue, ^{ // 同步 }); ???????dispatch_async(queue, ^{ // 异步 });

 

GCD的使用:

- (void)gcdTest { NSLog(@"currentThread---%@",[NSThread currentThread]); NSLog(@"---begin"); dispatch_queue_t queue = dispatch_queue_create(nil, 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(@"---end"); }

(1)同步+并发,同步+串行

2019-02-15 20:30:13.134436+0800 GCD[11324:226430] currentThread---<NSThread: 0x6000019dafc0>{number = 1, name = main} 2019-02-15 20:30:13.134613+0800 GCD[11324:226430]---begin 2019-02-15 20:30:15.135160+0800 GCD[11324:226430] 1---<NSThread: 0x6000019dafc0>{number = 1, name = main} 2019-02-15 20:30:17.136650+0800 GCD[11324:226430] 1---<NSThread: 0x6000019dafc0>{number = 1, name = main} 2019-02-15 20:30:19.138189+0800 GCD[11324:226430] 2---<NSThread: 0x6000019dafc0>{number = 1, name = main} 2019-02-15 20:30:21.139706+0800 GCD[11324:226430] 2---<NSThread: 0x6000019dafc0>{number = 1, name = main} 2019-02-15 20:30:23.141219+0800 GCD[11324:226430] 3---<NSThread: 0x6000019dafc0>{number = 1, name = main} 2019-02-15 20:30:25.142714+0800 GCD[11324:226430] 3---<NSThread: 0x6000019dafc0>{number = 1, name = main} 2019-02-15 20:30:25.142980+0800 GCD[11324:226430]---end

这两种结果都一样,虽然并发队列有执行多线程的能力,但是由于是同步执行,只会在单一线程中执行,所以会在同一线程中依次执行完任务后再添加下一个任务

(3)异步+串行

2019-02-15 20:42:52.282373+0800 GCD[11506:241315] currentThread---<NSThread: 0x600003b93640>{number = 1, name = main} 2019-02-15 20:42:52.282539+0800 GCD[11506:241315] ---begin 2019-02-15 20:42:52.282674+0800 GCD[11506:241315] ---end 2019-02-15 20:42:54.286775+0800 GCD[11506:241365] 1---<NSThread: 0x600003bf1f00>{number = 3, name = (null)} 2019-02-15 20:42:56.287246+0800 GCD[11506:241365] 1---<NSThread: 0x600003bf1f00>{number = 3, name = (null)} 2019-02-15 20:42:58.292730+0800 GCD[11506:241365] 2---<NSThread: 0x600003bf1f00>{number = 3, name = (null)} 2019-02-15 20:43:00.293612+0800 GCD[11506:241365] 2---<NSThread: 0x600003bf1f00>{number = 3, name = (null)} 2019-02-15 20:43:02.299173+0800 GCD[11506:241365] 3---<NSThread: 0x600003bf1f00>{number = 3, name = (null)} 2019-02-15 20:43:04.300262+0800 GCD[11506:241365] 3---<NSThread: 0x600003bf1f00>{number = 3, name = (null)}

当遇到异步执行时,会创建一个新的线程,主线程继续向下执行,新线程执行添加的任务,新线程是串行队列,也是按顺执行

(4)异步+并行

2019-02-15 20:47:46.005210+0800 GCD[11582:247831] currentThread---<NSThread: 0x600000071400>{number = 1, name = main} 2019-02-15 20:47:46.005361+0800 GCD[11582:247831] ---begin 2019-02-15 20:47:46.005466+0800 GCD[11582:247831] ---end 2019-02-15 20:47:48.005804+0800 GCD[11582:247876] 2---<NSThread: 0x600000012940>{number = 4, name = (null)} 2019-02-15 20:47:48.005805+0800 GCD[11582:247877] 1---<NSThread: 0x60000001c0c0>{number = 3, name = (null)} 2019-02-15 20:47:48.005804+0800 GCD[11582:247879] 3---<NSThread: 0x60000001c100>{number = 5, name = (null)} 2019-02-15 20:47:50.008365+0800 GCD[11582:247876] 2---<NSThread: 0x600000012940>{number = 4, name = (null)} 2019-02-15 20:47:50.008365+0800 GCD[11582:247879] 3---<NSThread: 0x60000001c100>{number = 5, name = (null)} 2019-02-15 20:47:50.008387+0800 GCD[11582:247877] 1---<NSThread: 0x60000001c0c0>{number = 3, name = (null)}

这种情况就是主线程在执行,同时其他开辟多个线程同时执行

(5)同步+主队列

这种情况下,如果调用的函数在其他线程执行,和同步+串行情况相同,但是在主线程下执行,打印完begin之后会死锁,程序崩溃。因为函数在主队列,他会执行完函数再调用添加进来的任务,但是任务不执行完就没法走完函数,有点像循环引用,A引用B,B引用A,双方都等对方执行完再执行,这种情况要避免。

GCD的其他方法

(1)栅栏方法:?????dispatch_barrier_async

NSLog(@"currentThread---%@",[NSThread currentThread]); NSLog(@"---begin"); dispatch_queue_t queue = dispatch_queue_create(nil, 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_barrier_async(queue, ^{ NSLog(@"barrier---%@",[NSThread currentThread]); }); dispatch_async(queue, ^{ //任务三 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; NSLog(@"3---%@",[NSThread currentThread]); } }); NSLog(@"---end");

结果

2019-02-16 00:02:50.327233+0800 GCD[13192:337727] currentThread---<NSThread: 0x6000010e2900>{number = 1, name = main} 2019-02-16 00:02:50.327496+0800 GCD[13192:337727] ---begin 2019-02-16 00:02:50.327658+0800 GCD[13192:337727] ---end 2019-02-16 00:02:52.333127+0800 GCD[13192:337760] 1---<NSThread: 0x600001087480>{number = 3, name = (null)} 2019-02-16 00:02:52.333147+0800 GCD[13192:337762] 2---<NSThread: 0x600001087500>{number = 4, name = (null)} 2019-02-16 00:02:54.335308+0800 GCD[13192:337760] 1---<NSThread: 0x600001087480>{number = 3, name = (null)} 2019-02-16 00:02:54.335321+0800 GCD[13192:337762] 2---<NSThread: 0x600001087500>{number = 4, name = (null)} 2019-02-16 00:02:54.335729+0800 GCD[13192:337762] barrier---<NSThread: 0x600001087500>{number = 4, name = (null)} 2019-02-16 00:02:56.336141+0800 GCD[13192:337762] 3---<NSThread: 0x600001087500>{number = 4, name = (null)} 2019-02-16 00:02:58.340345+0800 GCD[13192:337762] 3---<NSThread: 0x600001087500>{number = 4, name = (null)}

可以看出,栅栏函数能隔开加载到队列里的任务,未加载到队列里的任务无效。在栅栏函数前面的任务全部执行完后,执行栅栏函数,然后执行栅栏函数后的任务。

(2)延时执行方法:dispatch_after

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), queue, ^{ NSLog(@"after---%@",[NSThread currentThread]); });

顾名思义,就是延迟执行,上面代码是延迟3s,注意,这个延迟是3s后把任务加入队列,不是3s后就执行,如果你加入的是串行队列,3s后加入队列,但是前面的任务需要10s才执行完,那你得到结果的时间是13s。

(3)只执行一次:dispatch_once

static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ //任务 });

该函数能保证代码在程序运行过程中只执行一次,并且即使在多线程的环境下,也能保证里面的代码线程安全。

(4)快速迭代方法:dispatch_apply

NSLog(@"---begin"); dispatch_queue_t queue = dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT); dispatch_apply(10, queue, ^(size_t index) { NSLog(@"%zd---%@",index, [NSThread currentThread]); }); NSLog(@"---end"); 2019-02-16 00:32:36.142181+0800 GCD[13675:371444] ---begin 2019-02-16 00:32:36.142501+0800 GCD[13675:371494] 2---<NSThread: 0x6000019e0b00>{number = 4, name = (null)} 2019-02-16 00:32:36.142526+0800 GCD[13675:371495] 1---<NSThread: 0x6000019dfec0>{number = 3, name = (null)} 2019-02-16 00:32:36.142529+0800 GCD[13675:371444] 0---<NSThread: 0x600001985400>{number = 1, name = main} 2019-02-16 00:32:36.142537+0800 GCD[13675:371493] 3---<NSThread: 0x6000019e0a80>{number = 5, name = (null)} 2019-02-16 00:32:36.142644+0800 GCD[13675:371494] 4---<NSThread: 0x6000019e0b00>{number = 4, name = (null)} 2019-02-16 00:32:36.142678+0800 GCD[13675:371495] 5---<NSThread: 0x6000019dfec0>{number = 3, name = (null)} 2019-02-16 00:32:36.142693+0800 GCD[13675:371444] 6---<NSThread: 0x600001985400>{number = 1, name = main} 2019-02-16 00:32:36.142735+0800 GCD[13675:371493] 7---<NSThread: 0x6000019e0a80>{number = 5, name = (null)} 2019-02-16 00:32:36.142786+0800 GCD[13675:371494] 8---<NSThread: 0x6000019e0b00>{number = 4, name = (null)} 2019-02-16 00:32:36.142807+0800 GCD[13675:371495] 9---<NSThread: 0x6000019dfec0>{number = 3, name = (null)} 2019-02-16 00:32:36.143608+0800 GCD[13675:371444] ---end

dispatch_apply有点像for循环,如果是串行队列,和for差不多,依次N次任务加入到队列中,顺序执行,如果是并行队列,则是并发执行任务,从结果中看出,这个函数会调用主线程,不知道会不会在某些情况下造成阻塞。这个函数本身是同步的,所以会执行完全部任务,再执行下面的任务。

(5)dispatch_group

1、group基础

NSLog(@"currentThread---%@",[NSThread currentThread]); NSLog(@"group---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(@"1---%@",[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(@"2---%@",[NSThread currentThread]); }; }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; NSLog(@"3---%@",[NSThread currentThread]); }; NSLog(@"group---end"); }); 2019-02-16 01:16:04.203743+0800 GCD[14254:412235] currentThread---<NSThread: 0x600001b9a900>{number = 1, name = main} 2019-02-16 01:16:04.203910+0800 GCD[14254:412235] group---begin 2019-02-16 01:16:06.205824+0800 GCD[14254:412362] 1---<NSThread: 0x600001bf5000>{number = 3, name = (null)} 2019-02-16 01:16:06.205845+0800 GCD[14254:412365] 2---<NSThread: 0x600001bf5040>{number = 4, name = (null)} 2019-02-16 01:16:08.211355+0800 GCD[14254:412362] 1---<NSThread: 0x600001bf5000>{number = 3, name = (null)} 2019-02-16 01:16:08.211356+0800 GCD[14254:412365] 2---<NSThread: 0x600001bf5040>{number = 4, name = (null)} 2019-02-16 01:16:10.212927+0800 GCD[14254:412235] 3---<NSThread: 0x600001b9a900>{number = 1, name = main} 2019-02-16 01:16:12.214396+0800 GCD[14254:412235] 3---<NSThread: 0x600001b9a900>{number = 1, name = main} 2019-02-16 01:16:12.214740+0800 GCD[14254:412235] group---end

任务先加入到队列,然后队列加入到队列组,当队列组中的任务执行完成后,会执行dispatch_group_notify函数任务

2、wait

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

阻塞当前的线程,等到指定group中的任务执行完毕后再继续执行。

3、group_leave,group_enter

dispatch_group_enter ?????????标志着一个任务追加到group?,相当于group中未执行完毕的任务数+1

dispatch_group_leave ?????????标志着一个任务离开了group?,相当于group中未执行完毕的任务数-1

当group中未完成数为0的时候,才会执行notify和wait函数,这里有点像是引用计数。

(6)信号量dispatch_semaphore

dispatch_semaphore_create?:创建一个信号量

dispatch_semaphore_signal?:发送一个信号量,信号量+1

dispatch_semaphore_wait?:当线程信号量为0时阻塞线程,直到有新的信号量添加,当通过这个方法时,信号量的值-1

应用:保证线程安全,为线程加锁,或者保持线程同步,将异步执行任务转换成同步

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [NSThread sleepForTimeInterval:2]; NSLog(@"2---%@",[NSThread currentThread]); dispatch_semaphore_signal(semaphore); }); NSLog(@"1---%@",[NSThread currentThread]); dispatch_semaphore_wait(semaphore,time);//DISPATCH_TIME_FOREVER没有废弃时间 NSLog(@"3---%@",[NSThread currentThread]); 2019-02-16 15:35:58.372359+0800 GCD[17462:642975] 1---<NSThread: 0x600002a193c0>{number = 1, name = main} 2019-02-16 15:36:00.375787+0800 GCD[17462:643022] 2---<NSThread: 0x600002a718c0>{number = 3, name = (null)} 2019-02-16 15:36:00.376150+0800 GCD[17462:642975] 3---<NSThread: 0x600002a193c0>{number = 1, name = main}

创建一个信号量,由于线程任务是异步执行,所以开辟了一个新线程去执行,主线程继续执行下面的打印任务,当遇到dispatch_semaphore_wait时,信号量为0,阻塞主线程。新线程由于有耗时操作,2s后执行完任务,打印并且使信号量+1,这时主线程中信号量为1,所以通过了wait方法,继续执行下面的打印。这里说下dispatch_semaphore_wait的第二个参数,是指等待时间,如果超出这个时间,也会继续向下运行,但是这个信号量就已经废弃了,下面再遇到dispatch_semaphore_wait方法也不会起作用。

 

 

转载请注明原文地址: https://www.6miu.com/read-450094.html

最新回复(0)