Grand Central Dispatch(GCD)

时间:2022-05-16 14:46:49
 GCD     

GCD是异步执行任务的技术之一。

GCD使用很简洁的记述方法,实现了极为复杂繁琐的多线程编程。
dispatch_async(queue, ^{
     dispatch_async( dispatch_get_main_queue(), ^{
     //只在主线程可以执行的处理
     //例如用户界面更新
     });
});

NSObject中,提供了两个实例方法来实现简单的多线程技术:performSelectorInBackground:withObject  
 performSelectorOnMainThread。  我们也可以改用performSelector系方法来实现前面使用的GCD。
//NSObject  performSelectorInBackground:withObject:方法中执行后台线程
- (void)launchThreadByNSObject_performSelectorInBackground_withObject {
     [self performSelectorInBackground:@selector(doWork) withObject:nil];
}
- (void)doWork {
     @autoreleasepool{
          //长时间处理,   例如AR用画像识别    例如数据库访问
          [self performSelectorOnMainThread:@selector(doneWork) withObject:nil waitUntilDone:NO];
     }
}
- (void)doneWork {
     //例如用户界面更新
}
performSelector系方法确实比使用NSThread简单,但是相比GCD,却有所不及。
多线程编程
上下文切换:cpu的寄存器等信息保存到格子路径专用的内存块中,从切换目标路径专用的内存块中复原cpu寄存器等信息,继续执行切换路径的cpu命令。
使用多线程的程序可以在某个线程和其他线程之间反复多次进行上下文切换。
   
 应用程序启动的时候,通过最先执行的线程,即:主线程 来描绘用户界面、处理触摸屏幕的事件等。  
如果在该主线程中进行长时间的处理,就会妨碍主线程的执行(阻塞);  在os
x和ios中,会妨碍主线程中被称为RunLoop的主循环的执行,从而导致不能更新用户界面,应用程序的画面长时间停滞等问题。
如图:
Grand Central Dispatch(GCD)
Grand Central Dispatch(GCD)
但是当我们使用多线程,在执行长时间的处理时仍可以保证用户界面的响应性能。
GCD的API
1、Dispatch Queue
     apple:开发者要做的只是定义想执行的任务,并追加到适当的Dispatch Queue中。
dispatch_async(queue, ^{
     //想知性的任务
});
这样就可以使指定的Block在另一线程中执行了。
来看一下Dispatch Queue是什么:  执行处理的等待队列。 
我们通过dispatch_async函数等API, 在Block语法中记述想要执行的处理,并将其追加到Dispatch Queue中。 
而Dispatch Queue存在两种队列1、等待 Serial Dispatch Queue   2、不等待  Concurrent Dispatch Queue   如图:
Grand Central Dispatch(GCD)
Grand Central Dispatch(GCD)
不等待
的情况下,可以并行执行多个处理,但并行执行的处理数量取决于当前系统的状态:IOS和OS X 基于Dispatch
Queue中的处理数,CPU核数以及CPU符合等当前系统的状态来决定Concurrent Dispatch Queue中并行执行的处理数。
并行执行:使用多个线程来同时执行多个处理。  如图:
Grand Central Dispatch(GCD)
Grand Central Dispatch(GCD)
2、dispatch_queue_create
     通过此函数,可生成Dispatch Queue 如:
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);
其实我
们可以生成多个Dispatch Queue,  当生成多个Serial Dispatch Queue时,各个Serial Dispatch
Queue将并行执行,  虽然在1个Serial Dispatch Queue中
同事只能执行一个追加处理,但是如果将处理分别追加到4个Serial Dispatch Queue中,各个Serial Dispatch
Queue执行一个,
也就是4个同时处理, 如图:
Grand Central Dispatch(GCD)
Grand Central Dispatch(GCD)
我们如果过多的使用多线程,就会消耗大量的内存,引起大量的上下文切换,导致系统性能大幅度的降低。
但是要注意的是不能多个线程同时更新相同的数据, 如图:
Grand Central Dispatch(GCD)
Grand Central Dispatch(GCD)
如果想要并行执行,不发生数据竞争的问题,使用Concurrent Dispatch Queue。  对于他来说,不管生成多少,由于XNU内核只使用有效管理的线程,因此不会发生Serial Dispatch Queue的问题。
Grand Central Dispatch(GCD)
Grand Central Dispatch(GCD)
     在
dispatch_queue_create函数中,第一个参数指定Serial Dispatch Queue的名称。 Dispatch
Queue的名称推荐使用应用程序ID 这种逆序全程域名(FQDN, fully qualified domain name);
 他在xcode和Instruments的调试其中作为Dispatch Queue名称表示。  
并且,这个名称也会出现在应用程序崩溃时所产生的CrashLog中。
命名是应遵循:对我们编程人员来说简单易懂,对用户来说也要易懂。也可以为NULL,但是你调试中一个不会希望设置为NULL。
   
 Serial Dispatch Queue:dispatch_queue_t mySerialDispatchQueue =
dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);
   
 Concurrent Dispatch Queue:dispatch_queue_t mySerialDispatchQueue =
dispatch_queue_create("com.example.gcd.MySerialDispatchQueue",
DISPATCH_QUEUE_CONCURRENT);
     他的返回值是dispatch_queue_t类型。
 

dispatch_queue_t myConcurrentDispatchQueue
= dispatch_queue_create("com.example.gcd.MyConcurrentDispatchQueue",DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(myConcurrentDispatchQueue,
^{NSLog(@"block
on MyConcurrentDispatchQueue");});

     这样,代码在Concurrent Dispatch Queue中执行指定的Block。
这里要注意的是,即使有arc,我们也需要去手动释放Dispatch Queue。 因为他没有像Block那样具有作为oc对象来处理的技术。
     dispatch_release函数来释放。
     dispatch_release(mySerialDispatchQueue);
     有了release,也会对应的有retain了。
   
 dispatch_async函数中追加Block到Dispatch Queue后,即使立即释放Dispatch
Queue,该Dispatch Queue由于被Block持有,所以不会被废弃。所以Block可以执行。
Block执行结束后会释放Dispatch Queue,这时谁都不持有Dispatch Queue,所以他会被废弃。
3、Main Dispatch Queue/Global Dispatch Queue
     获取Dispatch Queue
     追加到Main Dispatch Queue的处理在主线程的RunLoop中执行, 由于在主线程中执行,因此要将用户界面的界面更新等一些必须在主线程中执行的处理追加到Main Dispatch Queue中使用。
     这与NSObject的performSelectorOnMainThread实例方法这一执行方法相同。
如图:
Grand Central Dispatch(GCD)
Grand Central Dispatch(GCD)
另一个
Global Dispatch Queue是所有应用程序都能够使用的Concurrent Dispatch
Queue,没有必要通过dispatch_queue_create函数逐个生成Concurrent Dispatch
Queue。只要获取Global Dispatch Queue使用就可以了。
   
 Global Dispatch Queue有四个优先级:High Priority, Default Priority, Low
Priority, Background Priority 。 (高,默认,低,后台)   通过XNU内核管理的用于Global
Dispatch Queue线程, 将各自使用的Global Dispatch Queue的执行优先级作为线程的执行优先级使用。
  向Global Dispatch Queue追加处理时,应选择与处理内容对应的执行优先级的Global Dispatch Queue。
     看一下Dispatch Queue的种类:
获取方法:
     //Main Dispatch Queue的获取方法
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
     //Global Dispatch Queue高优先级的获取方法
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
     //Global Dispatch Queue 默认优先级的获取方法
dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     //Global Dispatch Queue 低优先级的获取方法
dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
     //Global Dispatch Queue 后台优先级的获取方法
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
使用:
//默认优先级的Global Dispatch Queue中执行Block
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     //可并行执行的处理
     //在Main Dispatch Queue中执行Block
     dispatch_async(dispatch_get_main_queue(), ^{
          //只能在主线程中执行的处理
          });
     });
4、dispatch_set_target_queue
     他生成的Dispatch Queue都是默认优先级Global Dispatch Queue相同执行优先级的线程。 变更生成的执行优先级也需要使用他。
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create(“com.example.***”, NULL);
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);
   
 如果你在多个Serial Dispatch Queue中用dispatch_set_target_queue函数指定目标为某一个Serial
Dispatch Queue, 那么原先本应并行执行的多个Serial Dispatch Queue,在目标Serial Dispatch
Queue上只能同时执行一个处理:
Grand Central Dispatch(GCD)
Grand Central Dispatch(GCD)
     在必须将不可并行执行的处理追加到多个Serial Dispatch Queue中时, 如果使用dispatch_set_target_queue函数将目标指定为某一个Serial Dispatch Queue, 就可以防止处理并行执行。
5、dispatch_after
     延迟处理。  3秒如下:
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
     NSLog(@“waited at least three seconds.");
     });
dispatch_after是在指定时间追加到Dispatch Queue中。
6、Dispatch Group
     监视Dispatch Queue处理执行的结束。
dispatch_group_create();
dispatch_group_async();
dispatch_group_notify();
dispatch_group_wait();
7、dispatch_barrier_async
     访问数据库或者文件的时候,使用Serial Dispatch Queue可以避免数据竞争的问题。
     为了效率,读取处理可以追加到Concurrent Dispatch Queue中,写入处理在任一个读取处理没有执行的状态下,追加到Serial Dispatch Queue中。也就是说,在写入处理结束之前,读取处理不可执行。
   
 dispatch_barrier_async会等待追加到Concurrent DIspatch
Queue上的并行执行的处理全部结束之后,再将指定的处理追加到该Queue中。然后再由dispatch_barrier_async函数追加的处理
执行完毕之后,Concurrent Dispatch Queue才恢复为一般的动作,追加到该Queue的处理又开始并行执行。
如图:
Grand Central Dispatch(GCD)
Grand Central Dispatch(GCD)
8、dispatch_sync
     async:非同步,将指定的Block 非同步地追加到指定的Dispatch Queue中, dispatch_async函数不做任何等待,如图:
Grand Central Dispatch(GCD)
     sync:同步,将指定的Block 同步 追加到指定的Dispatch Queue中, 在追加Block结束之前,dispatch_sync会一直等待, 如图:
Grand Central Dispatch(GCD)
如:执行Main Dispatch Queue时,使用另外的线程Global Dispatch Queue进行处理,处理结束后立即使用所得到的结果,  这时要用sync。
Grand Central Dispatch(GCD)
sync在指定的处理执行结束之前,函数是不会返回的。
Grand Central Dispatch(GCD)
死锁:
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{NSLog(@“Hello");});
此时,在主线程中执行指定的Block,并等待其执行结束,但是主线程中正在执行这些源代码,所以不能追加到Main Dispatch Queue的Block。
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
     dispatch_sync(queue, ^{NSLog(@“hello");});
     });
此时,Main Dispatch Queue中执行的Block等待Main Dispatch Queue中要执行的Block执行结束。  死锁了。
9、dispatch_apply
     他是dispatch_sync函数和Dispatch Group的关联API。此函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理之行结束。
dispatch_apply(10, queue, ^(size_t index){NSLog(@“dsf");});
如果用在数组中,那么我们就不用编写for循环了。 直接[array count]
由于dispatch_apply函数与dispatch_sync函数相同,会等待处理执行结束,因此推荐在dispatch_async函数中非同步地执行dispatch_apply函数。
10、dispatch_suspend/dispatch_resume
     当追加大量处理到Dispatch Queue时, 在追加处理的过程中,有时希望不执行已追加的处理。  此时,我们需要挂起Dispatch Queue。当可以执行时再恢复。
suspend是挂起。
resume是恢复。
11、Dispatch Semaphore
     更新数据的时候,会产生数据不一致的情况,有时候应用程序还会异常结束。    虽然使用Serial Dispatch Queue和dispatch_barrier_async函数可避免这类问题,但是有必要进行更加细粒度的排他控制。
     Dispatch Semaphore是持有计数的信号。  计数0时,等待。计数1或者大于1时,减去1而不等待。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);  
参数表示计数的初始值,此时为1 。 
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
     此函数等待Dispatch Semaphore的计数值达到大于或等于1.  当计数大于1或者再等待中计数值大于等于1,对该计数进行减法并从dispatch_semaphore_wait函数返回。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//生成Dispatch Semaphore      Dispatch Semaphore的计数初始值设定为1      保证可访问NSMutableArray类对象的线程 同时只能有1个
dispatch_semaphore_t semahpore = dispatch_semaphore_create(1);
NSMutbaleArray *array = [[NSMutableArray alloc] init];
for (int i = 0; i < 100000; ++i) {
     dispatch_async(queue, ^{
          //等待Dispatch Semaphore   一直等待,知道Dispatch Semaphore 的计数值达到大于等于1.
               dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
          //由于dispatch Semaphore的计数值达到大于等于1,  所以将Dispatch Semaphore 的计数值减去1
          //dispatch_semaphore_wait函数执行返回。
          //执行到此时的Dispatch Semaphore的计数值恒为0.         由于可访问NSMutableArray累对象的线程只有一个,因此可安全的进行更新
               [array addObject:[NSNumber numberWithInt:i];
          //排他控制处理结束, 所以通过dispatch_semaphore_signal函数,将Dispatch Semaphore的计数值加1.
          //如果有通过dispatch_semaphore_wait函数等待Dispatch Semaphore的计数值增加的线程,就由最先等待的线程执行。
               dispatch_semaphore_signal(semaphore);
          });
}
//如果使用结束,需要以下这样 释放Dispatch Semaphore:  dispatch_release(semaphore);
12、dispatch_once
     保证应用程序执行中只执行一次指定处理的api。
     
     这里就是单例模式,在生成单例对象时使用。
13、Dispatch I/O
     一次使用多线程更快地并列读取文件。
     通过Dispatch I/O读写文件时,使用Global Dispatch Queue将一个文件按某个大小read/write。
     也可以将文件分割为一块一块地进行读取处理,分割读取的数据通过使用Dispatch Data可以更为简单地进行结合和分割 。
     dispatch_io_create  生成Dispatch IO, 指定发生错误时用来执行处理的Block,以及执行该Block的Dispatch Queue。
     dispatch_io_set_low_water函数 设定一次读取的大小(分割的大小),
     dispatch_io_read函数使用Global Dispatch Queue开始并列读取。