进程 线程 进程和线程的关系
多进程 多线程 任务 队列 iOS 中的多线程
一、进程
- 1.进程是一个具有一定独立功能的程序关于某次数据集合的一次运行活动, 它是操作系统分配资源的基本单元.
- 2.进程是指在系统中正在运行的一个应用程序, 就是一段程序的执行过程,我们可以理解为手机上的一个 App,
- 3.每个进程之间是相互独立的, 每个进程均运行在其专用且受保护的内存空间内 永远独立运行的所需要的全部资源.
二、线程
- 1.程序执行流的最小单元, 线程是进程中的一个实体
- 一个进程要想执行任务, 必须至少有一条线程, 应用程序启动的时候, 系统会默认开启一条线程, 也就是主线程
三、进程和线程的关系
- 1.线程是进程的执行单元, 进行所有的任务都在线程中进行
- 2.线程是 CPU 分配资源的调度的最小单元
- 3.一个程序可以对应多个进程(多进程), 一个进程中可以有多个线程, 但是至少有一条线程
- 通一个进程内的线程共享进程资源
四、多进程
打开 mac 的活动监视器 可以看到多个进程同时运行
- 进程是程序在计算机上的一次执行活动, 当你运行一个程序,你就启动了一个进程,显然, 程序是死的(静态的), 进程是活的(动态的)
- 进程可以分为系统进程和用户进程,凡是用于完成操作系统的各种功能的进程就是系统进程.他们就是处于运行状态下的操作系统本身.所有由用户启动的进程都是用户进程.进程是操作系统进行资源分配的单位.
- 进程又被细化为线程.也就是一个进程下多个能独立运行的更小的单位.在同一个时间里, 同一个计算机系统中如果允许两个或两个以上的进程处于运行状态, 这便是多进程.
五、多线程
- 1.同一时间, CPU只能处理一条线程, 只有一条线程在执行, 多线程并发执行, 其实是 CUP 在多条线程之间快速的切换, 如果 CPU 调度线程的时间足够快, 就造成了多线程并发执行的假象.
- 2.如果线程非常非常多, CUP 会在 N 多线程之间调度,消耗大量的 CPU 资源 每条线程被调度执行的频次会降低(线程的执行效率会降低)
- 3.多线程的优点:
能适当的提高程序的执行效率
能适当提高资源利用率(CPU、内存利用率) - 4.多线程的缺点
开启线程需要占用一定的内存空间(默认情况下, 主线程占用 1M, 子线程占用 512KB), 如果开启大量的线程, 会占用大量的内存空间, 降低程序的性能, 线程越多, CPU 在调度线程上的开销成本越大
线程设计更加复杂, 比如线程之间的通信, 多线程的数据共享
六、任务
就是执行操作的意思, 也就是在线程中执行的那段代码, 在 GCD 中是放在 block 中的, 执行任务的两种方式同步 sync和异步async
- 同步:同步添加任务到指定的队列中, 在添加的任务执行结束之前, 会一直等待, 直到队列里的任务完成之后再继续执行, 即会阻塞线程, 只能在当前线程中执行任务(是当前线程, 不一定是主线程), 不具备开启线程的能力
- 异步:线程会立即返回, 无需等待就会继续执行下面的任务, 不会阻塞当前线程, 可以在新的线程中执行任务, 具备开启新线程的能力(并不一定开启线程), 如果不是添加到主队列上, 异步会在子线程中执行任务.
七、队列
队列:这里的队列是指执行任务的队列, 即用来存放任务的队列, 队列是一种特殊的线性表, 采用先进先出的原则, 即新任务总是被插入到队列的末尾, 而读取任务的时候总是从队列的队头部开始读取, 每读取一个任务 则从队列中释放一个任务,
在 GCD 中, 有两种队列:串行队列和并发队列, 亮着都是先进先出的原则, 两者的主要区别是:执行顺序不同, 以及开启线程数不同.
串行队列(Serial Dispatch Queue):
同一时间内, 队列中只能执行一个任务, 只有当前的任务执行完之后, 才能执行下一个任务, (只开启一个线程, 一个任务执行完后, 再执行下一个任务). 主队列的主线程上的一个串行队列, 是系统自动的为我们创建的.
并发队列(Concurrent Dispatch Queue)
同时允许多个任务并发执行, (可以开启多个线程, 并且同时执行任务) 并发队列的并发功能只能在异步函数下生效
八、iOS 中的多线程
主要有三种:NSThread 、NSOperationQueue、GCD
1.NSThread:轻量级的多线程技术
是我们自己手动开辟的子线程, 如果使用的初始化方式就需要我们自己启动, 如果使用的是构造器方式它会自动启动, 只要是我们手动开辟的线程, 都需要我们自己管理该线程, 不只是启动, 还有该线程使用完毕后的资源回收.
开启一个常驻线程:
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(testThread:) object:@"我是参数"];
// 当使用初始化方法出来的主线程需要start启动
[thread start];
// 可以为开辟的子线程起名字
thread.name = @"NSThread线程";
// 调整Thread的权限 线程权限的范围值为0 ~ 1 。越大权限越高,先执行的概率就会越高,由于是概率,所以并不能很准确的的实现我们想要的执行顺序,默认值是0.5
thread.threadPriority = 1;
// 取消当前已经启动的线程
[thread cancel];
// 通过遍历构造器开辟子线程
[NSThread detachNewThreadSelector:@selector(testThread:) toTarget:self withObject:@"构造器方式"];
-
performSelector...只要是NSObject的子类或者对象都可以通过调用方法进入子线程和主线程,其实这些方法所开辟的子线程也是NSThread的另一种体现方式。
在编译阶段并不会去检查方法是否有效存在,如果不存在只会给出警告//在当前线程。延迟1s执行。响应了OC语言的动态性:延迟到运行时才绑定方法 [self performSelector:@selector(aaa) withObject:nil afterDelay:1]; // 回到主线程。waitUntilDone:是否将该回调方法执行完在执行后面的代码,如果为YES:就必须等回调方法执行完成之后才能执行后面的代码,说白了就是阻塞当前的线程;如果是NO:就是不等回调方法结束,不会阻塞当前线程 [self performSelectorOnMainThread:@selector(aaa) withObject:nil waitUntilDone:YES]; //开辟子线程 [self performSelectorInBackground:@selector(aaa) withObject:nil]; //在指定线程执行 [self performSelector:@selector(aaa) onThread:[NSThread currentThread] withObject:nil waitUntilDone:YES]
需要注意的是:如果是带afterDelay的延时函数,会在内部创建一个 NSTimer,然后添加到当前线程的Runloop中。也就是如果当前线程没有开启runloop,该方法会失效。在子线程中,需要启动runloop(注意调用顺序)
[self performSelector:@selector(aaa) withObject:nil afterDelay:1];
[[NSRunLoop currentRunLoop] run];
而performSelector:withObject:只是一个单纯的消息发送,和时间没有一点关系。所以不需要添加到子线程的Runloop中也能执行
2、GCD 对比 NSOprationQueue
我们要明确NSOperationQueue与GCD之间的关系
GCD是面向底层的C语言的API,NSOpertaionQueue用GCD构建封装的,是GCD的高级抽象。
1、GCD执行效率更高,而且由于队列中执行的是由block构成的任务,这是一个轻量级的数据结构,写起来更方便
2、GCD只支持FIFO的队列,而NSOperationQueue可以通过设置最大并发数,设置优先级,添加依赖关系等调整执行顺序
3、NSOperationQueue甚至可以跨队列设置依赖关系,但是GCD只能通过设置串行队列,或者在队列内添加barrier(dispatch_barrier_async)任务,才能控制执行顺序,较为复杂
4、NSOperationQueue因为面向对象,所以支持KVO,可以监测operation是否正在执行(isExecuted)、是否结束(isFinished)、是否取消(isCanceld)
实际项目开发中,很多时候只是会用到异步操作,不会有特别复杂的线程关系管理,所以苹果推崇的且优化完善、运行快速的GCD是首选
如果考虑异步操作之间的事务性,顺序行,依赖关系,比如多线程并发下载,GCD需要自己写更多的代码来实现,而NSOperationQueue已经内建了这些支持
不论是GCD还是NSOperationQueue,我们接触的都是任务和队列,都没有直接接触到线程,事实上线程管理也的确不需要我们操心,系统对于线程的创建,调度管理和释放都做得很好。而NSThread需要我们自己去管理线程的生命周期,还要考虑线程同步、加锁问题,造成一些性能上的开销.