iOS 中的多线程知识点

一、概念

1.1 进程

进程是程序在一个数据集合上运行的过程(注:一个程序有可能同时属于多个进程),它是CPU进行资源分配和调度的一个独立单位。

1.2 线程

线程,也被称为轻量进程(lightweight processes),指运行中的程序的调度单位。每1个进程至少要有1条线程。也可以认为线程是进程中的1条执行路径。

线程和进程比较:

  • 进程是CPU分配资源和调度的单位。
  • 线程是CPU调用(执行任务)的最小单位。
  • 一个程序可以对应多个进程,一个进程中可以有多个线程,但至少要有一个线程。
  • 同一个进程内的线程共享进程的资源。

1.3 串行、并行、并发

指的是任务的执行方式。
简单的说,串行是指多个任务时,各个任务按顺序执行,完成一个之后才能进行下一个。
并行指的是多个任务可以同时执行。
并发是指同一时间间隔内做多件事情”

并发与并行的比较:

  • 并发是两个任务可以在重叠的时间段内启动,运行和完成。并行是任务在同一时间运行,例如,在多核处理器上。
  • 并发是独立执行过程的组合,而并行是同时执行(可能相关的)计算。
  • 并发是一次处理很多事情,并行是同时做很多事情。
  • 应用程序可以是并发的,但不是并行的,这意味着它可以同时处理多个任务,但是没有两个任务在同一时刻执行。
  • 应用程序可以是并行的,但不是并发的,这意味着它同时处理多核CPU中的任务的多个子任务。
    一个应用程序可以即不是并行的,也不是并发的,这意味着它一次一个地处理所有任务。
    应用程序可以即是并行的也是并发的,这意味着它同时在多核CPU中同时处理多个任务。

1.4 同步异步

指的是能否开启新的线程。同步不能开启新的线程,异步可以。

1.5 句柄

句柄是整个windows编程的基础。一个句柄是指使用的一个唯一的整数值,即一个四字节长的数值,来标识应用程序中的不同对象和同类对象中的不同的实例,诸如,一个窗口、按钮、图标、滚动条、输出设备、控件或者文件等。句柄是windows用来标志应用程序中建立的或是使用的唯一整数,windows使用了大量的句柄来标志很多对象。应用程序能够通过句柄访问相应的对象的信息,但是句柄不是一个指针,程序不能利用句柄来直接阅读文件中的信息。如果句柄不用在I/O文件中,它是毫无用处的。

二、iOS 中的多线程

2.1 多线程的原理

同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)
多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)
如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象

2.2 多线程的优点

能适当提高程序的执行效率
能适当提高资源利用率(CPU、内存利用率)

2.3 多线程的缺点

开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
线程越多,CPU在调度线程上的开销就越大
程序设计更加复杂:比如线程之间的通信、多线程的数据共享

2.4 iOS 中的多线程实现方案

Aaron Swartz

NSOperation相对于GCD:

1,NSOperation拥有更多的函数可用,具体查看api。NSOperationQueue 是在GCD基础上实现的,只不过是GCD更高一层的抽象。

2,在NSOperationQueue中,可以建立各个NSOperation之间的依赖关系。

3,NSOperationQueue支持KVO。可以监测operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld)

4,GCD 只支持FIFO 的队列,而NSOperationQueue可以调整队列的执行顺序(通过调整权重)。NSOperationQueue可以方便的管理并发、NSOperation之间的优先级。

使用NSOperation的情况:各个操作之间有依赖关系、操作需要取消暂停、并发管理、控制操作之间优先级,限制同时能执行的线程数量.让线程在某时刻停止/继续等。

使用GCD的情况:一般的需求很简单的多线程操作,用GCD都可以了,简单高效。

从编程原则来说,一般我们需要尽可能的使用高等级、封装完美的API,在必须时才使用底层API。

当需求简单,简洁的GCD或许是个更好的选择,而Operation queue 为我们提供能更多的选择。

2.5 深入理解GCD

GCD有一个底层线程池,这个池中存放的是一个个的线程。之所以称为“池”,很容易理解出这个“池”中的线程是可以重用的,当一段时间后这个线程没有被调用胡话,这个线程就会被销毁。注意:开多少条线程是由底层线程池决定的(线程建议控制再3~5条),池是系统自动来维护,不需要我们程序员来维护

案例一

NSLog(@"1");
    
dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"2");
});
    
NSLog(@"3");

打印:
1

分析:奔溃在dispatch_sync(dispatch_get_main_queue() 报错:Thread 1: EXC_BREAKPOINT (code=1, subcode=0x101147818)

主队列:出队任务顺序 1 -> 3 -> 2
原因:任务3要等任务2执行完才能执行,任务2又排在任务3后面

案例二

     NSLog(@"1");
     
     dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     NSLog(@"2");
     });
     
     NSLog(@"3");

打印:
1
2
3

分析:
主队列:出队任务顺序 1 3
全局队列: 出队任务顺序 2
首先执行任务1,接下来会遇到一个同步线程,程序会进入等待。等待任务2执行完成以后,才能继续执行任务3。从dispatch_get_global_queue可以看出,任务2被加入到了全局的并行队列中,当并行队列执行完任务2以后,返回到主队列,继续执行任务3

案例三

dispatch_queue_t queue = dispatch_queue_create("com.example.SerialQueue", NULL);
     NSLog(@"1");
     dispatch_async(queue, ^{
     NSLog(@"2");
     dispatch_sync(queue, ^{
     NSLog(@"3");
     });
     NSLog(@"4");
     });
     NSLog(@"5");
     
     
     打印:
     1
     5
     2(5和2顺序不一定)

分析:
主队列:出队任务顺序 1 5
串行队列: 出队任务顺序 2 3 4
奔溃在dispatch_sync(queue 报错:Thread 6: EXC_BREAKPOINT (code=1, subcode=0x1007d3818)
奔溃原因:和前面案例一样,在一个串行队列里,任务4要等任务3执行完才能执行,任务3又排在任务4后面

案例四

 NSLog(@"1");
     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     NSLog(@"2");
     dispatch_sync(dispatch_get_main_queue(), ^{
     NSLog(@"3");
     });
     NSLog(@"4");
     });
     NSLog(@"5");
     
     
     打印:
     1
     5
     2
     3
     4

分析: 主队列 出队任务顺序: 1 5 3
全局队列 出队任务顺序: 2 4
任务2执行完以后,遇到同步线程。将同步线程中的任务加入到Main Queue中,这时加入的任务3在任务5的后面。当任务3执行完以后,没有了阻塞,程序继续执行任务4

案例五

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     NSLog(@"1");
     dispatch_sync(dispatch_get_main_queue(), ^{
     NSLog(@"2");
     });
     NSLog(@"3");
     });
     NSLog(@"4");
     while (1) {
     
     }
     NSLog(@"5");
     
     打印:
     4
     1

结果: 程序假死 死循环任务5之后的永远不会执行
主队列 任务出队顺序: 4(5)2
全局队列 任务出队顺序: 1 3
分析:任务1和任务4顺序不一定。任务4完成后,程序进入死循环,Main Queue阻塞。但是加入到Global Queue的异步线程不受影响,继续执行任务1后面的同步线程。同步线程中,将任务2加入到了主线程,并且,任务3等待任务2完成以后才能执行。这时的主线程,已经被死循环阻塞了。所以任务2无法执行,当然任务3也无法执行,在死循环后的任务5也不会执行。

案例六

dispatch_queue_t globelQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(globelQueue, ^{
        for (int i = 0; i < 1000000000; i ++) {
            
        }
        NSLog(@"dispatch_async  %@",[NSThread currentThread]);
    });
    
    dispatch_sync(globelQueue, ^{
        NSLog(@"dispatch_sync %@",[NSThread currentThread]);
    });
    
    NSLog(@" global_queue seconed blocks have completed");

分析:
队列进出原则: FIFO(first in first out) 先进先出
串行队列、并行队列的作用是确定任务可同时执行的数量。
同步方法、异步方法的作用是执行该任务是否开启新的线程。
所以串行队列里的任务也可能会在不同的线程内执行,只有一个任务在执行。

 打印信息如下
 for循环注释掉:
 2018-07-30 16:16:35.256546+0800 OCTest[5763:808762] dispatch_sync <NSThread: 0x60000006ddc0>{number = 1, name = main}
 2018-07-30 16:16:35.256556+0800 OCTest[5763:808928] dispatch_async  <NSThread: 0x604000278400>{number = 3, name = (null)}
 2018-07-30 16:16:35.256746+0800 OCTest[5763:808762]  global_queue seconed blocks have completed
 
 
 
 for循环打开:
 2018-07-30 16:24:58.585729+0800 OCTest[5835:820199] dispatch_sync <NSThread: 0x60400007dec0>{number = 1, name = main}
 2018-07-30 16:24:58.586576+0800 OCTest[5835:820199]  global_queue seconed blocks have completed
 2018-07-30 16:25:01.388374+0800 OCTest[5835:820286] dispatch_async  <NSThread: 0x6040004771c0>{number = 3, name = (null)}

案例七

dispatch_queue_t serialQueue = dispatch_queue_create("com.example.SerialQueue", NULL);
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.com.ConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(serialQueue, ^{
                for (int i = 0; i < 1000000000; i ++) {
        
                }
        NSLog(@"dispatch_async work hear on  %@",[NSThread currentThread]);
    });
    
    dispatch_sync(serialQueue, ^{
        NSLog(@"dispatch_sync more work here on %@",[NSThread currentThread]);
    });
    
    NSLog(@" queue_create Both blocks have completed");

 打印:
     2018-07-30 16:49:54.203409+0800 OCTest[6037:895620] dispatch_async work hear on  <NSThread: 0x60400027b5c0>{number = 3, name = (null)}
     2018-07-30 16:49:54.204128+0800 OCTest[6037:895414] dispatch_sync more work here on <NSThread: 0x6040002628c0>{number = 1, name = main}
     2018-07-30 16:49:54.204519+0800 OCTest[6037:895414]  queue_create Both blocks have completed

分析:
什么是串行队列?什么是并行队列?

 并发 Concurrent: tasks are dequeued in FIFO order, but run concurrently and can finish in any order.(任务以FIFO从序列中移除,然后并发运行,可以按照任何顺序完成。它会自动开启多个线程同时执行任务)
 
 串行 Serial: tasks execute one at a time in FIFO order(任务以FIFO从序列中一个一个执行。一次只调度一个任务,队列中的任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)而且只会开启一条线程)

Aaron Swartz

备注:本文部分内容参考http://www.cocoachina.com/ios/20171129/21362.html

发布了19 篇原创文章 · 获赞 4 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/aluoshiyi/article/details/81204495