进程
如图示,一个程序对应一个进程,即程序和进程是一对概念,它们分别描述了一个程序的静态形式和动态特征,
同时,进程还是操作系统进行资源分配的基本单位。
每个进程之间是独立的地址空间,每个进程均运行在其专用的且受到保护的内存中。
进程定义:
- 狭义: 进程即正在过行程序的实例
- 广义: 进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。
简而言之,就是程序是一个没有生命的实体,只有在处理器在执行一个程序时,这个执行过程称为进程。
另外,Linux 操作系统也可以凭借CPU快速在多个进程之间进行切换,即进程间的上下文切换。无论切换速度如何,在同一时刻正在运行的进程仅会有一个。周时切换 CPU 正在运行的进程是需要代价的,例如,内核此刻要换下正在 CPU 上运行的进程A,并让 CPU 开始运行进程B,在换下进程A之前,内核必须要及时保存进程A的运行状态,如果进程B不是第一次执行,那么内核必须将进程B上次运行的状态以及保存的信息,这种在进程换出换入期间必须要做的任务即为进程切换。
线程
线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位或者说是基本执行单元。程序启动,会默认开启一条线程,称为主线程或 UI线程。
一个进程中可以并发多个线程,每条线程并行执行不同的任务,多个线和共享本进程的地址空间。
当某一线程的处理不需要占用CPU而只资源打交道时,让需要占用CPU资源的其它线程有机会获得CPU资源。从根本上说,这就是多线程编程的最终目的.
简单说就是,多线程的目的不是为了提高运行效率,而是为了提高资源使用效率,为了最大限度地利用CPU资源。但开辟线程,又会占用 CPU 资源,浪费内存空间,所以多线程的使用需要慎重克制。
注意
(默认情况下,主线程占用1M,子线程占用512KB)
- 多线程编程的目的,就是”最大限度地利用CPU资源
- 多线程优点主要是把占据时间长的程序中的任务放到后台去处理,让程序的运行速度可能加快。
- 大量的线程,也会影响性能,因为操作系统需要在它们之间切换。线程也需要开辟内存空间。
多线程
基于手动管理线程生命周期很繁琐,而且容易出错,因此应尽可能避免使用pthread和NSThread。所以GCD和NSOperation的使用率更高。
方案 | 简介 | 语言 | 生命周期 | 使用频率 |
---|---|---|---|---|
Pthread | 通用API,跨平台,使用难度大 | C | 程序员管理 | 几乎不用 |
NSThread | 面向对象,简单易用,可直接操作线程 | OC | 程序员管理 | 偶尔使用 |
GCD | 充分利用设备的多核。 | C | 程序员管理 | 经常使用 |
NSOperation | 面向对象 基于GCD,功能更多 | OC | 程序员管理 | 经常使用 |
NSThread *main = [NSThread currentThread];
//NSThread
if(self.t == nil || self.t.isCancelled || self.t.isFinished){
self.t = [[NSThread alloc]initWithTarget:self selector:@selector(study:) object:@100];
[self.t start];
}
//主线程和子线程均是512(官方文档上说是1M)
NSLog(@"%lu====%lu",main.stackSize/1024, self.t.stackSize/1024);
//隐式`的多线程调用方法
[self.p performSelectorInBackground:@selector(study:) withObject:@5000];
//GCD
disaptch_async(dispatch_get_global_queue(0,0),^{
});
复制代码
线程的生命周期是
新建 – 就绪 – 运行 – 阻塞 – 死亡
- 新建:创建线程实例
- 就绪:向线程发出star消息,并把线程加入可调度线程池
- 运行:被cpu调度执行任务时
- 堵塞:线程被堵塞,如调用sleep,同步锁,栅栏函数等
- 死亡:任务执行完毕,并且没有新任务需要执行,或者线程被强制退出
注意:
[NSThread exit]:一旦强行终止线程,后续的所有代码都不会被执行
[thread cancel]取消:并不会直接取消线程,只是给线程对象添加 isCancelled 标记
复制代码
线程池
corePoolSize:核心线程数
maxPoolSize: 最大线程数
-
如果小于corePoolSize, 就创建线程并执行该任务;否则,将该任务放入阻塞队列;
-
如果不能将任务放入阻塞队列中,说明阻塞队列已满;那么尝试创建一个新的线程去执行这个任务;如果执行失败,说明线程池中线程数达到maxPoolSize,则执行reject()处理任务;
线程池的饱和策略: 当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:
- AbortPolicy:丢弃任务并抛出RejectedExecutionException异常,阻止系统正常运行
- CallerRunsPolicy:将任务回退到调用者
- DiscardOldestPolicy:丢弃等待最久的任务,然后重新尝试执行任务(重复此过程)
- DisCardPolicy:直接丢弃任务
线程安全
多线程的时候多个线程可能会访问同一块资源,这样就很容易引发数据错乱和数据安全等问题,这时候就需要我们保证每次只有一个线程访问这一块资源,锁 应运而生。
图示各种锁的效率,
1. NSLock
NSLock 实现了最基本的互斥锁
由于是互斥锁,当一个线程进行访问的时候,该线程获得锁,其他线程进行访问的时候,将被操作系统挂起,直到该线程释放锁,其他线程才能对其进行访问,从而却确保了线程安全。但是如果连续锁定两次,则会造成死锁问题。
NSLock * cjlock = [NSLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
NSLog(@"线程1加锁成功");
sleep(2);
[lock unlock];
NSLog(@"线程1解锁成功");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
[lock lock];
NSLog(@"线程2加锁成功");
[lock unlock];
NSLog(@"线程2解锁成功");
});}
复制代码
2. @synchronized
-
synchronized(obj) 指令使用的 obj 为该锁的唯一标识,只有当标识相同时,才为满足互斥,线程 3 中使用 self 做标识的,所以线程 3 不会被阻塞。
-
@sychronized(obj){} 内部 obj 被释放或被设为 nil 不会影响锁的功能,但如果 obj 一开始就是 nil,那就会丢失了锁的功能了。
-
@synchronized 指令实现锁的不需要在代码中显式的创建锁对象,便可以实现锁的机制,但作为一种预防措施,@synchronized 块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。所以如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。
NSObject * obj = [NSObject new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(obj){
NSLog(@"线程1开始");
sleep(3);
NSLog(@"线程1结束");
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
@synchronized(obj){
NSLog(@"线程2");
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(self){
NSLog(@"线程2");
} });
}
复制代码
输出如下:
- 线程1开始
- 线程3
- 线程1结束
- 线程2
3. dispatch_semaphore
dispatch_semaphore 使用信号量机制实现锁,等待信号和发送信号。
通过三个想着函数实现,一个是创建信号量,一个是等待信号,一个是发送信号。
dispatch_semaphore 的机制就是当有多个线程进行访问的时候,只要有一个获得了信号,其他线程的就必须等待该信号释放。
//参数必须大于或等于 0,否则 dispatch_semaphore_create 会返回 NULL
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 6 * NSEC_PER_SEC);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(semaphore, overTime);
NSLog(@"线程1开始");
sleep(5);
NSLog(@"线程1结束");
dispatch_semaphore_signal(semaphore);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
dispatch_semaphore_wait(semaphore, overTime);
NSLog(@"线程2开始");
dispatch_semaphore_signal(semaphore);
});
复制代码
线程与Runloop 关系
-
runloop与线程是一一对应的,一个runloop对应一个核心的线程.
为什么说是核心的,是因为runloop是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局的字典里。
-
runloop是来管理线程的,当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务。
-
runloop在第一次获取时被创建,在线程结束时被销毁。
-
对于主线程来说,runloop在程序一启动就默认创建好了。
-
对于子线程来说,runloop是懒加载的,只有当我们使用的时候才会创建,所以在子线程用定时器要注意:确保子线程的runloop被创建,不然定时器不会回调。