前言
终于来到了多线程这一篇章,多线程我个人觉得是整个OC里面比较重要的一环,因为不管是从底层(lisdispatch)还是到应用层,都是使用非常广泛的。本文主要是对整个多线程原理作一个简单地梳理,然后结合最常使用的GCD作进一步的解释。
什么是线程?什么是进程?
-
线程是进程的基本执行单元,一个进程的所有任务都在线程中执行。
-
进程要想执行任务,必须得有线程,进程至少要有一条线程
-
程序启动会默认开启一条线程,这条线程被称为主线程或 UI 线程
-
进程是指在系统中正在运行的一个应用程序
-
每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间内
-
进程有内存空间,线程没有空间
-
同一进程内的线程共享本进程的资源,但是进程之间的资源是独立的。
-
进程的崩溃不会影响其他进程,但是线程的崩溃会直接导致所在的进程崩溃
-
进程之前的切换需要花费更多的资源,而线程会更优。
-
线程是处理器调度的基本单位,但是进程不是。
由于IOS是单进程,如果只是单线程,会导致任务进度缓慢,所以我们需要多线程。
意义
优点
-
能适当提高程序的执行效率
-
能适当提高资源的利用率(CPU,内存)
-
线程上的任务执行完成后,线程会自动销毁
缺点
-
开启线程需要占用一定的内存空间(默认情况下,每一个线程都占 512 KB)
-
如果开启大量的线程,会占用大量的内存空间,降低程序的性能
多线程原理
CPU具有调度能力,在让一个线程去处理一个任务的时候,他不会等待他执行完成,而是会切换到其他线程,调度其他线程执行其他任务,因为执行速度很快,所以根本感知不到,同一时间只有一个线程在执行任务,如果要实现真正意义上的并发,那就是多核CPU。
线程的状态
线程状态
:当一个线程被创建的时候,并不会立即执行,而是进入runable
准备阶段,等待cpu的调度,cpu一旦调度就会进入到runing
,running
之后要么死亡,要么会阻塞
,之后会重新进入到runable
等待调度。
可调度线程池
:线程的创建不是
顺序的。当新来一个任务的时候,首先会判断当前可调度线程池中是否存在空闲
的线程,如果存在,安排线程去执行,如果不存在,再判断是否达到了阈值
,如果没有,那么就会把这条任务放到队列中,再安排线程执行。如果都已经饱满
并且都处于执行状态
,就会交给饱和策略
。
饱和策略
:1.抛出异常。 2.任务回退给调用者。 3.丢掉等待过久的任务 4.丢弃任务。
任务执行
任务的执行速度的影响因素
1.cpu 2.任务的复杂度 3.优先级 4.线程状态
优先级翻转
IO密集型
:频繁
等待的线程。
CPU密集型
:很少
等待的线程。
对于频繁等待的线程,因cpu具有调度能力,会对该线程提升优先级,但提升了并不是意味着会立即执行,仅仅是提高优先级。
优先级因素
:1.用户指定 2.频繁等待程度 3.长时间不执行
锁
在多线程中,因为在同一个进程中,资源是共享的,所以多线程必然存在着资源的竞争,那么就引入了锁的概念。
自旋锁
:发现线程正在执行,不停地询问,资源消耗比较高,典型的就是atomic。适用于短小精简的任务。
互斥锁
:发现线程正在执行,进入休眠,等待唤醒,资源消耗低。
atomic
ios属性的默认值,原子属性,单写多读,需要消耗大量资源。
GCD简介
一套c语言的api。
GCD 是苹果公司为多核的并行运算提出的解决方案
GCD 会自动利用更多的CPU内核(比如双核、四核)
GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码
串行队列与并发队列
串行队列
:任务之间是顺序的,挨个排好队。
并发队列
:任务之间调度是并发的,任务之间的调度是不确定
的,1234任务都有可能,如果是比较长的任务一时完不成可能会挂起
,其他任务完成快的会先执行,先调度并不定会先执行。任务的执行依赖于线程
。
死锁简单案例
dispatch_queue_t queue = dispatch_queue_create("cooci", NULL);
NSLog(@"1");
// 异步函数
dispatch_async(queue, ^{
NSLog(@"2");
// 同步
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
复制代码
分析: 首先这是一个串行队列,串行队列执行是顺序的,走到1,发现异步函数,可以缓一缓,接下来去5,然后进入到异步函数,走到2也没问题,接来来是一个同步函数,同步函数的执行堵塞了4的执行,4的执行需要等3执行完才会执行,但是3的执行是后来添加的,并且是dispatch_sync同步的,需要等4执行完才可以执行,互相等待对方执行造型死锁。
dispatch_queue_t queue = dispatch_queue_create("cooci", **NULL**);
NSLog(@"1");
// 异步函数
dispatch_async(queue, ^{
NSLog(@"2");
// 同步
dispatch_sync(queue, ^{
NSLog(@"3");
});
});
NSLog(@"5");
复制代码
分析:前面和上个案例一样,在执行3的时候,由于是同步函数,需要等待最外层的异步函数执行完毕才会执行,但是它本身就在这个异步函数内部,所以走不完,造成死锁。
WB面试题
dispatch_queue_t queue = dispatch_queue_create("com.lg.cooci.cn", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"1");
});
dispatch_async(queue, ^{
NSLog(@"2");
});
dispatch_sync(queue, ^{ NSLog(@"3"); });
NSLog(@"0");
dispatch_async(queue, ^{
NSLog(@"7");
});
dispatch_async(queue, ^{
NSLog(@"8");
});
dispatch_async(queue, ^{
NSLog(@"9");
});
复制代码
分析:这是一个串行队列,串行队列中,异步函数不存在先后顺序,1、2是无序的,如果不考虑任务的复杂程度来看,12谁先完成是不确定的,包括3,123都是并列
的,(同理789)也是不存在堵塞
,直到0,3以后的任务因为同步函数
而堵塞,但可以肯定的是0在789的前面,所以正确的顺序可能不唯一,123在0前,789在0后即可。