谈谈对多线程和GCD的理解

多线程(GCD)

GCD是异步执行任务的技术之一,通过Dispatch Queue来控制任务的执行,线程管理由系统实现,比以前更加有效率。

1.多线程编程

多线程原理

当mac或者iPhone打开一个app的时候,首先就将包含在应用程序中的CPU命令列配置到内存中,CPU从应用程序指定的地址开始,一个一个地执行CPU命令列。即便地址分散在各处,也是一条无分叉的路径。

image

这里的“一个CPU执行的CPU命令列为一条无分叉路径”就是“线程”。

在1个CPU核工作下的多线程中,这个CPU可以“同时”执行多条不同路径上的命令,实现多线程,但基于CPU一个时间只能执行一个内存块的原则,CPU其实是每隔一段时间在不同路径上切换执行(被称为“上下文切换”),使看起来像同时在执行一样。

现在一个物理CPU芯片有64核(64个CPU),一台计算机会有多个CPU核同时在工作,这就不是看起来像同时在执行了,可能其他的CPU分担了一些路径执行。

多线程解决的事情:

简单来说:多线程可以保证应用程序的响应性能。

在iOS程序启动时,通过主线程绘制页面、响应触摸事件。如果在主线程中有耗时的操作,就会妨碍主线程(Runloop主循环)的执行,从而不能更新用户界面,画面停滞等问题。

image

使用多线程,可以在长时间处理时,仍然能保证用户界面的响应性能。

多线程容易发生的问题:

  • 多个线程更新相同的资源会导致数据不一致

  • 停止等待时间的线程会导致多个线程相互持续等待(死锁)

  • 使用太多线程会消耗大量内存

image

2.GCD原理

Dispatch Queue执行处理的等待队列。定义想执行的任务并追加到适当的Dispatch Queue中,Dispatch Queue按照追加的顺序(FIFO)执行处理。

  • Serial Dispatch Queue在一条队列上,按照FIFO的顺序执行追加的任务,后追加的任务需要等待前追加的执行完才能执行。

  • Concurrent Dispatch Queue由XNU内核管理多条任务链,不等待且不分顺序的执行任务。

同步 & 异步

  • 同步(dispatch_sync)将指定任务追加到指定的队列中,并等待这个追加任务的结束。

  • 异步(dispatch_async)将指定任务追加到指定的队列中,不等待这个追加任务的结束,继续向后执行。

Concurrent Dispatch Queue并发队列原理

iOS和OS X核心—— XNU内核决定应当使用的线程数,并只生成所需的线程数处理任务,当处理结束时,应当执行的处理数减少时,XNU内核会结束不再需要的线程。假如当Concurrent Dispatch Queue执行8个任务的时候,由于开辟线程会有内存小号,XNU并不一定会开8个线程在8条不同的队列上同时执行8个任务,而可能像下图这样用4个Concurrent Dispatch Queue用线程执行:

image

执行顺序会根据处理内容和系统状态发生改变调整。

Serial Dispatch Queue串行队列原理

在执行顺序不能改变或不想并发执行任务的时候使用Serial Dispatch Queue,但是Serial Dispatch Queue比Concurrent Dispatch Queue能生成更多的线程,如果有1000个任务就会创建1000个Serial Dispatch Queue,会大量的占用系统的资源。

  1. 同步串行(dispatch_sync + Serial Dispatch Queue)

    不新增线程

    • 在当前串行队列下:线程暂停执行当前队列的任务(当前线程进入无限时间的等待直到新指派的任务完成),新指派的任务根据FIFO法则排在当前队列任务的后面(线程进入无限时间等待前一个任务执行结束),互相等待,引起线程死锁

    • 在新增串行队列下:暂停当前队列中的任务(当前队列进入无限时间的等待直到新指派的任务完成),新指派的任务在新的队列中,线程切换上下文到新队列执行新任务结束后回到原队列(等到结束),原队列线程继续执行原队列任务。

  2. 同步并发(dispatch_sync + Concurrent Dispatch Queue)不新增线程线程暂停执行当前队列的任务,由于并发型队列并行执行数量由内核XNU控制,新任务被派发到新队列中,当前线程切换上下文到新队列执行新派发的任务,执行结束后,切换回原队列。

  3. 异步串行(dispatch_async + Serial Dispatch Queue)新增线程

    • 新增一条队列:新派发的任务被按FIFO的顺序放置在新增的这条队列中,在新增的多条线程中随机选择一条,依次执行队列上的任务,其余线程闲置,直到此队列被释放。

    • 新增多条队列:新派发的任务被各自放置在不同的串行队列中,每个队列对应一条线程,相互执行没有时间顺序,效果等同于并发,但是性能不如并发,因为创建几条队列就会有几条线程数,数量过多时会影响系统性能,而并发是由XNU内核控制并调整线程数的。

  4. 异步并发(dispatch_async + Concurrent Dispatch Queue)新增线程,也新增队列并行执行的处理数量取决于当前的系统状态,即XNU内核基于队列中的处理数、CPU核数以及CPU负荷等当前系统的状态来决定并行的处理数,任务的执行顺序会根据处理内容和系统状态发生改变。

3.GCD使用

dispatch_release在iOS6.0\macOS10.8以后就不需要使用了

3.1 Dispatch Queue创建的dispatch queue对象名称为:dispatch_queue_t

创建方式:

//参数1 队列的名称,使用FQDN的命名方式,有助于后期的debug,会出现在程序崩溃时候的crashLog中
//参数2 串行队列为DISPATCH_QUEUE_SERIAL或NULL  并发队列为DISPATCH_QUEUE_CONCURRENT
dispatch_queue_t queueName = dispatch_queue_create("com.example.gcd.queueName",NULL);

3.2 Main Dispatch Queue && Global Dispatch Queue

  • Main Dispatch Queue:主线程所在的队列,只有一个,属于serial队列,更新UI等处理需要在此队列中执行

  • Global Dispatch Queue:全局队列,属于concurrent队列,有四个优先级

获取Main Dispatch Queue:

dispatch_queue_t mainQueue = dispatch_get_main_queue();

获取Global Dispatch Queue:

//global dispatch queue 的四个优先级
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
​
//参数1 优先级
//参数2 备用参数,一般填0
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

组合使用:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
 //并发处理一些耗时的任务
 dispatch_async(dispatch_get_main_queue(), ^{
 //回到主线程,做更新UI等操作
 });
});

3.3 dispatch_set_target_queue

  • 设置队列的优先级

  • 建立队列的执行阶层

当使用dispatch_queue_create创建队列的时候,不管是串行还是并行,它们的优先级都是DISPATCH_QUEUE_PRIORITY_DEFAULT级别。

//设置队列的优先级
//参数1 被设优先级的队列
//参数2 目标队列
//将参数1的优先级设置成参数2的优先级
dispatch_set_target_queue(changedQueue, targetQueue);
​
//设置队列的执行阶层
//参数1 被指派的队列
//参数2 目标队列
//将changedQueue的任务指派到targetQueue中,如果targetQueue是串行,则changedQueue也是串行,反之亦然
//如果targetQueue是串行可以防止changedQueue并行执行
dispatch_set_target_queue(changedQueue, targetQueue);

3.4 dispatch_after

在指定的时间异步追加任务到指定队列,而不是指定时间执行任务。

//时间单位  NSEC:纳秒 USEC:微秒 SEC:秒 PER:每
#define NSEC_PER_SEC 1000000000ull  //每秒有多少纳秒
#define NSEC_PER_MSEC 1000000ull  //每毫秒有多少纳秒
#define USEC_PER_SEC 1000000ull  //每秒有多少微秒
#define NSEC_PER_USEC 1000ull  //每微秒有多少纳秒
​
//相对时间
//参数1:时间起点  DISPATCH_TIME_NOW现在   DISPATCH_TIME_FOREVER永久以后
//参数2:时间偏移量  ull是C语言的数值字面量  detal的单位是纳秒
dispatch_time_t time = dispatch_time(dispatch_time_t when, int64_t delta);
//绝对时间 (如2011年11月11日11时11分11秒)
//参数1:struct timespec类型的时间
dispatch_time_t wallTime = dispatch_walltime(const struct timespec * _Nullable when, int64_t delta);
//在time时间时,将block追加到queue中
dispatch_after(time, queue, ^{ 
});

绝对时间的使用方式:

//将NSDate对象转换成dispatch_time_t对象的方法
- (dispatch_time_t)getDispatchTimeByDate:(NSDate *)date
{
 NSTimeInterval interval = date.timeIntervalSince1970;
 double second, subsecond;
 subsecond = modf(interval, &second);
 struct timespec time;
 time.tv_sec = second;
 time.tv_nsec = subsecond * NSEC_PER_SEC;
 dispatch_time_t walltime = dispatch_walltime(&time, 0);
 return walltime;
}

3.5 dispatch_group

用来监听多个并发执行任务全部执行结束

//创建一个group
dispatch_group_t group = dispatch_group_create();
//创建全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//添加若干个并发队列执行任务
dispatch_group_async(group, queue, ^{});
dispatch_group_async(group, queue, ^{});
//当所有并发队列执行结束后,触发此方法,将任务追加到指定的队列
dispatch_group_notify(group, dispatch_get_main_queue(), ^{});

dispatch_group_wait不仅能做到等待group中的并发任务结束,还能设置等待一定的时间。等待的原理就是执行dispatch_group_wait的线程被暂停执行任务,当时间到或者group里的并发任务全部执行完毕,激活dispatch_group_wait的执行线程并返回结果。而dispatch_group_notify是简化dispatch_group_wait使用的一种方式。

//用dispatch_group_wait实现等待一秒
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
//时间到或者group结束之前,线程被停止在这里,不往下执行。
long result = dispatch_group_wait(group, time);
//当时间到了,或者group出结果了,result得到结果
if(result == 0) {
 //group执行结束
} else {
 //时间到
}

3.6 dispatch_barrier_async

在并发队列中的等待追加的处理全部执行结束后串行追加到queue中,一般用来划分存取数据(排他控制),实现高效率数据库访问。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//任务被最先追加到队列中
dispatch_async(queue, ^{});
//执行dispatch_barrier_async的线程开始无限时等待,重新执行的信号是之前的queue中的任务都执行完毕
dispatch_barrier_async(queue, ^{});//开始执行时,任务被追加到串行队列,后续的任务开始排队
//在barrier任务执行结束后,后续任务才开始执行
dispatch_async(queue, ^{});

dispatch_groupdispatch_set_target_queue也能实现按序等待,但是源代码会很复杂。

3.8 dispatch_apply

在指定的队列上重复执行一定的次数,并等待全部处理执行结束。

形式上与dispatch_syncdispatch_group组合效果一样,为了避免阻塞主线程,推荐在dispatch_async中使用。

dispatch_queue_t globalQueue = dispatch_queue_create("com.example.gcd.queueName",DISPATCH_QUEUE_CONCURRENT);
//把当前线程喊停,往globalQueue上依次追加10个任务
dispatch_apply(10, globalQueue, ^(size_t index) {
 NSLog(@"%zu",index);
});
//只有等10个任务全部处理结束了,当前线程才会继续往后走,中间这段时间线程阻塞
NSLog(@"done");

3.9 dispatch_suspend && dispatch_resume

  • dispatch_suspend:挂起已追加但是尚未执行的队列,如果已经开始执行的,就无法挂起了

  • dispatch_resume:恢复执行队列

//挂起队列
dispatch_suspend(queue);
//恢复队列            
dispatch_resume(queue);

3.10 Dispatch Semaphore

Semaphore 信号量在多线程开发中被广泛使用,主要的用途是等待异步任务的结束。当主线程在进入一段异步线程代码之前,获取一个信号量,然后进入等待阶段,异步代码段完成后,异步线程释放信号量,主线程捕获到信号量后,继续执行。

dispatch_semaphore_create(long value); // 创建信号量
dispatch_semaphore_signal(dispatch_semaphore_t deem); // 发送信号量
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); // 等待信号量

3.11 dispatch_once

多线程下百分之百安全的做到只执行一次

//获取静态变量once
static dispatch_once_t once;
dispatch_once(&once, ^{
 //里面的内容可以保证只被执行一次
});

你可能感兴趣的:(谈谈对多线程和GCD的理解)