一、基础概念
1.进程
- 是一个具有独立功能的程序,操作系统分配资源的基本单元
- 好比如说,我们打开一个iOS的APP,他在操作系统上运行之后,这就成为系统上的一个进程了(在其他平台可以是多进程的)
- 进程之间是独立的,互不影响的
2.线程
- 是用来执行任务的最小单位
- 线程是进程中的一个实体,线程组成进程
线程与进程的关系:线程是进程的执行单位,进程里的线程可以进行资源共享
3.任务
- 线程里的那段要执行的代码。
- 执行方式:
- 同步执行(Sync) —— 不具备开线程的能力,会一直等待前面的任务完成后才会执行,会阻塞线程
- 异步执行(Async) —— 具备开启线程的能力(不一定就能开,看队列),无需等待就能继续执行当前任务下面的其他任务,不阻塞线程
4.队列
- 队列是一种常见的数据结构,特殊的线性表。特点为:先进先出(FIFO原则)。
- 作用:解决一些任务执行和等待的公平性,先进者先服务。
- 分为串行队列和并发队列,两者都符合 FIFO(先进先出)的原则。
4.1.串行队列:
- 任务一个接一个的来执行,必须等前一个任务完成,下一个任务才开始执行。不管有多少条线程进入到这种队列,都必须排队开始。
4.2.并发队列:
- 同时允许多个任务并发执行。多线程的优势可以在里面重发显现,效率高。
综合关系:任务通过线程来执行,线程是任务的载体,任务在队列中排序等待执行,任务的执行由队列开启线程来完成。而这一切都是包含在进程里,进程的顺利运行必须依靠大家一起完成。
下面展示一个进程下同步队列的简单示例图:
二、iOS中常见的多线程方案
1.开启线程的方式
- pthread(c) — 可以跨平台,下面三种都是基于pthread
- NSThread(OC) — 使线程变得可操作,有线程对象,线程的生命周期可以手动管理
- GCD(C) — 可以充分地利用设备的多核,生命周期自动管理
- NSOperation(OC) — 是对GCD的封装,使用上更面向对象,生命周期自动管理
2.多线程机制:
2.1.同步异步函数 + 串行并行队列 (两两组合,四种情况)
问题:使用了 异步函数 就一定能开启多线程了吗?
答:不是!
原因:在iOS里面能开启线程的只有两种情况:
1. 异步+并发队列
2.异步+手动创建的串行队列特别说明:主队列是特殊的串行队列,不管和什么函数配合都不能开启多线程
2.2.死锁
- 死锁:任务之间相互等待,造成线程阻塞
- 情况:同步函数 + 串行队列 (两个条件同时存在,就会造成死锁)
2.3.线程安全
线程之间是可以共享资源的,但是这会造成由于资源抢夺而导致的一些线程不安全问题。为了解决线程安全的问题,我们需要对共享的资源进行加锁,让线程有序地访问。
2.3.1.常用锁的种类
锁的作用:保证资源的完整性,你访问,我就不能访问
- 互斥锁:
- 简单的保证共享资源的完整性,确保我操作,你就不能操作
- @synchronized
- NSLock
- 自旋锁:
- 和互斥锁很像,不同点在于资源被占用时,他不会休息,而是一直轮循去询问资源是否可用
- OSSpinLock:
- 可以达到效果,但是存在优先级反转的问题。就是如果等待的线程优先级比较高,那么会一直占用CPU(相当于执行一个while循环),导致原来持有资源的低等级的线程无法释放锁,从而一直卡死
- os_unfair_lock:解决优先级反转的问题
- 递归锁:
- 防止一条线程里多次加锁从而造成死锁的情况。场景:递归
- NSRecursiveLock
- 信号量:
- 可以控制线程的并发数量
- dispatch_semaphore_t
- 读写锁:
- 把写和读分隔开,如我们常用的栅栏函数就是读写锁
- dispatch_barrier_async / dispatch_barrier_sync
- 条件锁:
- 线程检测器根据条件决定是否要执行
- NSCondition
- NSConditionLock
2.3.2.控制线程的执行顺序
- 解决场景:A、B、C三个任务执行完,再执行D
2.3.2.1. 信号量
1.1.创建信号
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
1.2.等待信号: 让信号量 -1
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
...需要执行的任务
1.3.释放信号:让信息量 +1(只有当上面semaphore的值大于或等于0时,之前在等待的线程才会一条条的执行)
...任务执行完成,释放信号
dispatch_semaphore_signal(semaphore);
2.3.2.2. 栅栏函数
注意:
1. 栅栏函数对于自己创建的并发队列才有意义,不能使用系统的全局并发队列,因为这样会把一些系统的操作给卡住
2. dispatch_barrier_sync: 会把代码写在这函数下面的所有操作都给卡住,知道他执行完,不管是否在同一队列!
3. dispatch_barrier_async: 只会把同一队列里往下的操作给卡住,写在这函数下面的其他队列的操作不受影响
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"开始任务A");
[NSThread sleepForTimeInterval:1];
NSLog(@"任务A finish.");
});
dispatch_async(queue, ^{
NSLog(@"开始任务B");
[NSThread sleepForTimeInterval:0.5];
NSLog(@"任务B finish.");
});
dispatch_async(queue, ^{
NSLog(@"开始任务C");
[NSThread sleepForTimeInterval:0.2];
NSLog(@"任务C finish.");
});
dispatch_barrier_async(queue, ^{
NSLog(@"============ barrier ===========");
});
dispatch_async(queue, ^{
NSLog(@"开始任务D");
[NSThread sleepForTimeInterval:0.2];
NSLog(@"任务D finish.");
});
2.3.2.3.dispatch_group
- 创建group:dispatch_group_create()
- Enter group:dispatch_group_enter(group)
- Leave group:dispatch_group_leave(group)
- 所有任务完成之后,回调:dispatch_group_notify()
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"开始任务A");
[NSThread sleepForTimeInterval:3];
NSLog(@"任务A finish.");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"开始任务B");
[NSThread sleepForTimeInterval:2];
NSLog(@"任务B finish.");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"开始任务C");
[NSThread sleepForTimeInterval:1];
NSLog(@"任务C finish.");
dispatch_group_leave(group);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"开始任务D");
});
2.3.2.4. NSBlockOperation依赖
NSOperationQueue *queue=[[NSOperationQueue alloc] init];
//创建操作
NSBlockOperation *operationA=[NSBlockOperation blockOperationWithBlock:^(){
NSLog(@"执行第1次操作,线程:%@",[NSThread currentThread]);
}];
NSBlockOperation *operationB=[NSBlockOperation blockOperationWithBlock:^(){
NSLog(@"执行第2次操作,线程:%@",[NSThread currentThread]);
}];
NSBlockOperation *operationC=[NSBlockOperation blockOperationWithBlock:^(){
NSLog(@"执行第3次操作,线程:%@",[NSThread currentThread]);
}];
NSBlockOperation *operationD=[NSBlockOperation blockOperationWithBlock:^(){
NSLog(@"执行第4次操作,线程:%@",[NSThread currentThread]);
}];
//添加依赖
[operationA addDependency:operationB];
[operationB addDependency:operationC];
[operationC addDependency:operationD];
//将操作添加到队列中去
[queue addOperation:operationA];
[queue addOperation:operationB];
[queue addOperation:operationC];
[queue addOperation:operationD];
注意:
1. 如果对ABC的任务执行顺序也要有顺序,那么只能使用信号量、栅栏函数、NSOperationQueue
2. 其中栅栏函数每个任务开始都使用栅栏dispatch_barrier_async,不使用dispatch_async即可保证按序进行