iOS 单核&多核,进程&线程,串行&并行,同步&异步

热门标签

特别声明:文章多为网络转载,资源使用一般不提供任何帮助,特殊资源除外,如有侵权请联系!

先了解一些基本概念:

单核&多核:一个处理器(CPU)有几个运算核心,来区别是单核还是多核。
单核和多核的本质区别就是同一时刻可以运行几个线程,单核只能运行一个,N核可以运行N个。那么问题来了,单核CPU是如何多线程“同时”运行的?为什么同时会用引号,因为本质上单核CPU运行多线程不是同时的,同时只是一种假象,它的运行原理是时间片进行的,即操作系统分配时间片给ABCDE...线程,在不同时间片内运行不同线程,由于每个时间片很短所以看起来是多个线程同时进行的,这就是所谓的并发。

进程&线程:进程是表示资源分配的的基本概念,又是调度运行的基本单位,是系统中的并发执行的单位。线程是CPU调度的基本单元。这样表述简直太抽象,简单说,运行一个程序就会开启一个进程,一个进程可以有多个线程,一个线程只能属于一个进程。

并发:拥有处理多个任务的能力,不一定要同时,不同代码块交替执行的性能,可以串行处理也可以并行处理
并行:同时处理多个任务的能力,不同代码块同时执行的性能
串行:指多个任务时,各个任务按顺序执行,完成一个之后才能执行下一个

同步:就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去(注意死锁原因分析);
异步:是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。
比如:你叫我去吃饭,我听到了就立刻和你去吃饭,如果我没有听到,你就会一直叫我,直到我听见和你一起去吃饭,这个过程叫同步;异步过程指你叫我去吃饭,然后你就去吃饭了,而不管我是否和你一起去吃饭。而我得到消息后可能立即就走,也可能过段时间再走。

异步、同步 & 并行、串行的特点

iOS 单核&多核,进程&线程,串行&并行,同步&异步
2.png

线程、任务和队列的概念

iOS 单核&多核,进程&线程,串行&并行,同步&异步
1.png

同步、异步和串行、并行针对的对象是什么?
同步、异步是对于线程而言的。同步,不开启新线程;异步会开启新线程。
串行、并行是针对队列里面的任务来说的。串行就是任务一个一个按顺序做,并行就是多任务同时进行。

同步、异步和串行、并行组合的结果

iOS 单核&多核,进程&线程,串行&并行,同步&异步
3.png

简要解释:
异步并行队列: 就是这个队列的任务是并行执行的,也就是每一个任务都会开辟一个新线程,这些任务同时执行。
异步串行队列:重新开辟一个新线程,这个串行队列里面的任务会在这个线程按顺序一个一个执行。
同步并行队列(串行队列):首先不会开辟新线程,所以不管是串行队列还是并行队列,队列里面的任务都在当前线程执行,所以所有任务按顺序一个一个执行。

关于死锁的形成:

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSLog(@"=================1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"=================2");
    });
    NSLog(@"=================3");
}

看这段代码就是一个死锁,为什么会造成死锁呢?

GCD Queue 分为三种:
1、The main queue :主队列,主线程就是在个队列中。
2、Global queues : 全局并发队列。
3、用户队列:是用函数 dispatch_queue_create 创建的自定义队列

dispatch_sync 和 dispatch_async 区别:
dispatch_async(queue,block) async 异步队列,dispatch_async 函数会立即返回, block会在后台异步执行。
dispatch_sync(queue,block) sync 同步队列,dispatch_sync 函数不会立即返回,及阻塞当前线程,等待 block同步执行完成。

分析上面代码:

viewDidLoad 在主线程中, 及在dispatch_get_main_queue() 中,执行到sync 时向dispatch_get_main_queue()插入 同步 thread1.

sync 会等到 后面block 执行完成才返回, sync又在dispatch_get_main_queue() 队列中,它是串行队列,sync 是后加入的,前一个是主线程,所以 sync 想执行 block 必须等待主线程执行完成,主线程等待 sync 返回,去执行后续内容。
造成死锁,sync 等待mainThread 执行完成, mianThread 等待sync 函数返回。

串行队列中添加了block,block一直等到前面的任务处理完才会执行,从而导致了死锁。我这样理解,mianThread这时候有两个任务,一个是dispatch_sync,另一个是block。dispatch_sync收到返回信息的时候这个任务才会结束,但是dispatch_sync收到返回信息是在block执行结束,所以简单说,就是sync这是一个在主线程中进行的任务,想要执行block必须等主线程完成这个任务,但是这个任务的完成必须要block执行结束,然后就死锁了。

iOS GCD详细介绍——点击进入

了解了这些基本概念后,下面重点深入探讨同步&异步、串行&并行的使用及场景。

Critical Section 临界区
就是一段代码不能被并发执行,也就是,两个线程不能同时执行这段代码。这很常见,因为代码去操作一个共享资源,例如一个变量若能被并发进程访问,那么它很可能会变质(译者注:它的值不再可信)。

Race Condition 竞态条件
这种状况是指基于特定序列或时机的事件的软件系统以不受控制的方式运行的行为,例如程序的并发任务执行的确切顺序。竞态条件可导致无法预测的行为,而不能通过代码检查立即发现。

Deadlock 死锁
两个(有时更多)东西——在大多数情况下,是线程——所谓的死锁是指它们都卡住了,并等待对方完成或执行其它操作。第一个不能完成是因为它在等待第二个的完成。但第二个也不能完成,因为它在等待第一个的完成。

Thread Safe 线程安全
线程安全的代码能在多线程或并发任务中被安全的调用,而不会导致任何问题(数据损坏,崩溃,等)。线程不安全的代码在某个时刻只能在一个上下文中运行。一个线程安全代码的例子是 NSDictionary 。你可以在同一时间在多个线程中使用它而不会有问题。另一方面,NSMutableDictionary 就不是线程安全的,应该保证一次只能有一个线程访问它。

Context Switch 上下文切换
一个上下文切换指当你在单个进程里切换执行不同的线程时存储与恢复执行状态的过程。这个过程在编写多任务应用时很普遍,但会带来一些额外的开销。

  • 在加载控制器的时候,一些比较费时的操作都异步执行,这样不会阻塞主线程push控制器。

下面是一个关于在 dispatch_async 上如何以及何时使用不同的队列类型的快速指导:

  1. 自定义串行队列:当你想串行执行后台任务并追踪它时就是一个好选择。这消除了资源争用,因为你知道一次只有一个任务在执行。注意若你需要来自某个方法的数据,你必须内联另一个 Block 来找回它或考虑使用 dispatch_sync。
  2. 主队列(串行):这是在一个并发队列上完成任务后更新 UI 的共同选择。要这样做,你将在一个 Block 内部编写另一个 Block 。以及,如果你在主队列调用 dispatch_async 到主队列,你能确保这个新任务将在当前方法完成后的某个时间执行。
  3. 并发队列:这是在后台执行非 UI 工作的共同选择。

不知道何时适合使用 dispatch_after ?

  1. 自定义串行队列:在一个自定义串行队列上使用 dispatch_after 要小心。你最好坚持使用主队列。
  2. 主队列(串行):是使用 dispatch_after 的好选择;Xcode 提供了一个不错的自动完成模版。
  3. 并发队列:在并发队列上使用 dispatch_after 也要小心;你会这样做就比较罕见。还是在主队列做这些操作吧。

dispatch_once()
以线程安全的方式执行且仅执行其代码块一次。试图访问临界区(即传递给 dispatch_once 的代码)的不同的线程会在临界区已有一个线程的情况下被阻塞,直到临界区完成为止。常用于单例。

Dispatch barriers
Dispatch barriers 是一组函数,在并发队列上工作时扮演一个串行式的瓶颈。使用 GCD 的障碍(barrier)API 确保提交的 Block 在那个特定时间上是指定队列上唯一被执行的条目。这就意味着所有的先于调度障碍提交到队列的条目必能在这个 Block 执行前完成。

当这个 Block 的时机到达,调度障碍执行这个 Block 并确保在那个时间里队列不会执行任何其它 Block 。一旦完成,队列就返回到它默认的实现状态。 GCD 提供了同步和异步两种障碍函数。

注意到正常部分的操作就如同一个正常的并发队列。但当障碍执行时,它本质上就如同一个串行队列。也就是,障碍是唯一在执行的事物。在障碍完成后,队列回到一个正常并发队列的样子。

下面是你何时会——和不会——使用障碍函数的情况:

  1. 自定义串行队列:一个很坏的选择;障碍不会有任何帮助,因为不管怎样,一个串行队列一次都只执行一个操作。
  2. 全局并发队列:要小心;这可能不是最好的主意,因为其它系统可能在使用队列而且你不能垄断它们只为你自己的目的。
  3. 自定义并发队列:这对于原子或临界区代码来说是极佳的选择。任何你在设置或实例化的需要线程安全的事物都是使用障碍的最佳候选。

dispatch_barrier_async和dispatch_barrier_sync
分析下面代码,把dispatch_barrier_sync改成dispatch_barrier_async,看打印

dispatch_queue_t queue = dispatch_queue_create("thread", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        sleep(3);
        NSLog(@"test1");
    });
    dispatch_async(queue, ^{
        NSLog(@"test2");
    });
    dispatch_sync(queue, ^{
        NSLog(@"test3");
    });
    dispatch_barrier_sync(queue, ^{ ///分界线在这里 请注意是同步的
        sleep(1);
        for (int i = 0; i<50; i++) {
            if (i == 10 ) {
                NSLog(@"point1");
            }else if(i == 20){
                NSLog(@"point2");
            }else if(i == 40){
                NSLog(@"point3");
            }
        }
    });
    NSLog(@"hello");
    dispatch_async(queue, ^{
        NSLog(@"test4");
    });
    NSLog(@"world");
    dispatch_async(queue, ^{
        NSLog(@"test5");
    });
    dispatch_async(queue, ^{
        NSLog(@"test6");
    });

dispatch_barrier_sync 打印结果:

 

iOS 单核&多核,进程&线程,串行&并行,同步&异步
dispatch_barrier_sync

dispatch_barrier_async 打印结果:

 

iOS 单核&多核,进程&线程,串行&并行,同步&异步
dispatch_barrier_async

hello、world位置的区别就是两种方式的区别。

可以看出,不管那种方式queue队列中任务执行顺序都是barrier前->barrier里->barrier后。区别在于,dispatch_barrier_sync阻碍了主线程任务的执行,而dispatch_barrier_async则没有阻碍。

下面是一个快速总览,关于在何时以及何处使用 dispatch_sync :

  1. 自定义串行队列:在这个状况下要非常小心!如果你正运行在一个队列并调用 dispatch_sync 放在同一个队列,那你就百分百地创建了一个死锁。
  2. 主队列(串行):同上面的理由一样,必须非常小心!这个状况同样有潜在的导致死锁的情况。
  3. 并发队列:这才是做同步工作的好选择,不论是通过调度障碍,或者需要等待一个任务完成才能执行进一步处理的情况。

Dispatch Groups(调度组)
Dispatch Group 会在整个组的任务都完成时通知你。这些任务可以是同步的,也可以是异步的,即便在不同的队列也行。而且在整个组的任务都完成时,Dispatch Group 可以用同步的或者异步的方式通知你。因为要监控的任务在不同队列,那就用一个 dispatch_group_t 的实例来记下这些不同的任务。

  • 第一种是 dispatch_group_wait ,它会阻塞当前线程,直到组里面所有的任务都完成或者等到某个超时发生。
  • 另一种是dispatch_group_notify,有点就是不会阻塞当前线程。

使用简介:

dispatch_group_t downloadGroup = dispatch_group_create();
dispatch_group_enter(downloadGroup);
dispatch_group_leave(downloadGroup);

dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
        
 });

dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); // 5 
        dispatch_async(dispatch_get_main_queue(), ^{ // 6 
           
 });

dispatch_apply
表现得就像一个 for 循环,但它能并发地执行不同的迭代。这个函数是同步的,所以和普通的 for 循环一样,它只会在所有工作都完成后才会返回。

for循环是走完一次迭代后再走下一次迭代,按顺序一次一次走完,dispatch_apply是像瀑布一样一起执行所有迭代。

那么问题来了:为什么我们经常用for循环,却不用dispatch_apply?
当在 Block 内计算任何给定数量的工作的最佳迭代数量时,必须要小心,因为过多的迭代和每个迭代只有少量的工作会导致大量开销以致它能抵消任何因并发带来的收益。而被称为跨越式(striding)的技术可以在此帮到你,即通过在每个迭代里多做几个不同的工作。

注意:你创建并行运行线程而付出的开销,很可能比直接使用 for 循环要多。若每个迭代是非常大的集合(即每个迭代要操作很多东西),那才应该考虑使用 dispatch_apply,否则可能得不偿失。

标签:

未经允许不得转载:作者:SheaYang, 转载或复制请以 超链接形式 并注明出处 技术狗|博客
原文地址:《iOS 单核&多核,进程&线程,串行&并行,同步&异步》 发布于2019-10-14

分享到:
赞(0)

评论 抢沙发

7 + 1 =


iOS 单核&多核,进程&线程,串行&并行,同步&异步

长按图片转发给朋友

Vieu4.0主题
专业打造轻量级个人企业风格博客主题!专注于前端开发,全站响应式布局自适应模板。

登录

忘记密码 ?

您也可以使用第三方帐号快捷登录

Q Q 登 录
微 博 登 录