线程和进程的定义
- 线程是进程的基本执行单元,一个进程中的所有任务都在线程中执行,进程想要执行任务,必须至少有一个线程,程序启动默认会开启一条线程,这条线程被称为主线程或者UI线程.
- 进程是系统中正在运行的一个程序,每个进程之间是相互独立的,每个进程都运行在其专用且受保护的内存空间内,在mac系统中可以通过
活动监视器来查看所有的进程.
关于线程和进程更多详细的内容,可以参照程序员的自我修养这本书.
线程和进程之间的关系
地址空间:同一进程中的线程共享本进程的地址空间,而进程之间则是独立的地址空间 资源拥有:同一进程内的线程共享本进程的资源,比如,内存,I/O,CPU等等,但是进程之间的资源是独立的.
- 一个进程崩溃是不会对别的进程有影响的,但是一个线程的崩溃会导致进程崩溃。所以多进程是要比多线程健壮的.
- 进程切换,消耗的资源比较大。
- 执行过程:每个独立的线程都有一个唯一的程序入口,顺序执行序列。但是线程不能独立执行,线程必须依存在进程中,由应用程序提供多个线程执行控制。
- 线程是处理器的调度单位,进程不是。
- 线程没有地址空间,线程包含在进程的地址空间中.
多线程的意义
优点:
- 能适当的提高程序执行的效率
- 能适当的提高资源的利用率(CPU,内存)
- 线程上的任务执行完成后,线程会自动销毁
缺点:
- 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占用512KB, 当然这取决于机器的配置)
- 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
- 线程越多,CPU在调用线程的开销就越大
- 程序设计更加负责,比如线程间的通信,多线程的数据共享
任务的执行速度的影响因素
- CPU的调度情况
- 任务的复杂度
- 优先级
- 线程的状态
优先级翻转, 介绍两种线程类型
- I/O 密集型,频繁等待
- CPU 密集型,很少等待
针对上述两种线程, 相对于I/O 密集型线程,CPU密集型的线程更容易得到优先级提升, 但是这样I/O密集型就有可能被饿死,但是CPU又做了调度,当一个线程等待了一段时间,就开始提升,等待时间越长,就会优先级越高. 那么这就被称为优先级翻转。
优先级的影响因素
- 创建线程的时候,可以指定优先级
- 等待的频繁度
- 长时间不执行,也会提高优先级
自旋锁和互斥锁
当我们对一个资源进行访问的时候,有可能两个两个线程同时对资源进行操作,那么这样就会引起资源状态的异常,那么怎么解决这个问题呢?下面介绍两种锁
- 自旋锁,发现其他线程执行,当前线程询问,忙等,耗费性能比较高
- 互斥锁,发现其他线程执行,当前线程休眠(挂起),线程等待唤醒
当然自旋锁的存在是有意义的,如果都是访问一个资源,发现别的线程持有,就挂起,当别的线程释放,就唤醒,就会涉及到上下文切换,如果这个资源在别的线程在很短的时间就进行释放了,那这个线程挂起再唤醒,会浪费CPU性能。 所以当一个资源竞争很激烈,且资源持有的时间很短,就可以用自旋锁. 那么在ios/macos开发的时候,我们什么时候用过自旋锁? 相信大家在声明一个属性的时候都用过atomic这个关键字,那么底层在实现这个属性的setter的方法的时候就是用的自旋锁. 如下图所示
GCD
GCD: 就是将任务添加到队列,并且执行执行任务的函数. GCD 是apple 为多核的并行运算提出的解决方案. GCD 会自动利用更多的CPU内核,GCD 会自动管理线程的生命周期(创建线程,调度任务,销毁线程)
串行队列和并发队列
串行队列是一个一个执行的, 在同一个线程执行. 而并发队列是一个一个调度的,把每个任务一次调度给某个线程去执行,所以并发队列的执行顺序是不一定的.
举一个例子
上述代码执行的顺序是什么呢?答案应该是1 5 2 3 4. 那如果把队列改成串行队列呢?
当改成串行队列的时候,执行到
dispatch_sync的时候,就会阻塞当前的执行,直到执行完成,但是之前async的时候,这个任务并没有执行完毕,串行队列必须要等前一个任务执行完毕,所以就死锁了.