问题: 简述一下多线程的相关内容.
回答目录:
- 线程与进程.
- 多线程的方案.
- 同步、异步、串行、并发.
- 死锁.
- GCD队列组.
- iOS相关的锁.
- 实现线程同步的方案.
- atomic.
- IO 操作的读写安全
- GCD问题实操.
进程与线程:
-
本质:
进程是一个静态的容器,可以理解为正在执行的应用程序实例,它里面容纳很多线程; 线程是一系列方法的 线性 执行路径(CPU维度). -
资源共享与同步:
进程拥有独立的资源空间(资源分配基本单位),共享起来比较复杂,常使用IPC进行同步,同步简单; 线程之间共享所属的进程空间,共享起来比较简单,但是同步较为麻烦,常使用加锁的方式进行同步. -
崩溃:
进程崩溃不会影响到其他进程; 一个线程的崩溃会导致整个进程的崩溃.
iOS的多线程主要有一下几种方案.
技术方案 | 简介 | 语言 | 线程声明周期 | 使用频率 |
---|---|---|---|---|
pthread | 一套通用的多线程API; 适用于Unix/Linux/Windows; 跨平台\可移植; 使用难度较大 | C | 程序员管理 | 几乎不用 |
NSThead | 使用更加面向对象; 简单易用,可以直接操作线程对象 | OC | 程序员管理 | 偶尔使用 |
GCD | 旨在替换NSThread等多线程技术; 可以充分利用设备的多核 | C | 自我管理 | 经常使用 |
NSOperation | 底层基于GCD; 比GDD多了一些更简单使用的功能; 使用更加面向对象 | 0C | 程序员管理 | 经常使用 |
知识补充点: 同步、异步、串行、并发
同步、异步主要影响:
能不能开启新的线程
- 同步: 在当前线程执行任务,不具备开启新的线程的能力.
- 异步: 在新的线程执行任务,具备开启新线程的能力.
串行、并发主要影响:
任务的执行方式
- 串行: 一个任务执行完成之后,再去执行另外一个任务.
- 并发: 多个任务同时执行.
并发队列 | 手动创建的串行队列 | 主队列 | |
---|---|---|---|
同步dispatch_sync | 不开启新线程 串行执行 | 不开启新线程 串行执行 | 不开启新线程 串行执行 |
异步dispatch_async | 开启新线程 并发执行 | 开启新线程 串行执行 | 不开启新线程 串行执行 |
** 问题: 什么叫死锁? **
线程的各个任务相关等待则会导致死锁问题.
使用 sync
往当前串行队列中添加任务就会造成死锁问题.
示例:
- (void)viewDidLoad {
NSLog(@"Log1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^(){
NSLog(@"Log2");
});
NSLog(@"Log3");
}
分析如下所示.
- viewDidLoad 整个方法作为任务添加到主队列中.并且在主线程中执行.
- 在打印完成Log1之后.立即添加任务(打印
Log2
)到主队列中,并且要求立即执行(sync的缘故).
- 由于是串行队列,当前队列中还有任务(ViewDidLoad),故
任务2
需要等待任务1
完成.
- 想要完成
ViewDidLoad任务
(任务1),那么立刻打印Log2就必须要执行,也就是任务2必须要完成.
- 最终两个任务相互等待,造成死锁.
GCD_Group 队列组
通过下述的函数可以实现执行完一定量的任务之后,再执行别的任务.
// 创建任务组
dispatch_group_t group = dispatch_group_create();
// (在group完成之后再去执行别的任务)
// 激活任务组
dipatch_group_notify(group, queue, ^(){
// 执行某些任务
});
锁相关内容:
常应用于 线程同步 问题的解决.
OSSpinLock
OSSpinLock叫做自旋锁,等待锁的线程会处于忙等的状态,一直占用CPU资源.
目前现在已经不安全,可能出现优先级反转问题.如果等待锁的线程优先级比较高,它会一直占用CPU资源,导致优先级较低的线程无法释放锁.
OSSpinLock需要导入 <libkern/OSAtomic.h>
OSSpinLock 主要的函数有:
-
SpinLock初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
-
SpinLock加锁
OSSpinLockLock(&lock);
-
SpinLock解锁
OSSpinLockUnlock(&lock);
os_unfair_lock
os_unfair_lock 用来取代不安全的OSSpinLock,在iOS10之后支持.
os_unfair_lock 不再是自旋锁类型,加锁等待的线程会处于休眠状态,并非忙等.
os_unfair_lock 需要导入 <os/lock.h>
os_unfair_lock 相关函数有:
-
初始化锁
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
-
尝试加锁
os_unfair_lock_trylock(&lock);
-
加锁
os_unfair_lock_lock(&lock);
-
解锁
os_unfair_lock_unlock(&lock);
pthread_mutex
pthread_mutex 是互斥锁,等待锁的线程会进入休眠状态
需要导入头文件 <pthread.h>
pthread_mutex 相关函数:
-
初始化锁
// 静态初始化 pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
// 初始化方式2 pthread_mutex_t lock; pthread_mutexattr_t attr; pthread_mutex_init(&lock, &attr);
-
设置锁的类型
#define PTHREAD_MUTEX_NORMAL 0 默认锁类型 #define PTHREAD_MUTEX_ERRORCHECK 1 可捕获错误的锁类型 #define PTHREAD_MUTEX_RECURSIVE 2 递归锁类型 #define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL
pthread_mutex_settype(&attr, PTHREAD_MUTEX_DEFAULT);
-
销毁属性
pthread_mutex_destroy(&attr);
-
加锁
pthread_mutex_lock(&lock);
-
解锁
pthread_mutex_unlock(&lock);
-
销毁锁
pthread_mutex_destroy(&lock);
pthread_mutex 条件
-
条件初始化
pthread_cond_t cond; pthread_cond_init(&cond, NULL);
-
条件阻塞
- 进入休眠,放开锁
- 得到通知之后,退出休眠,并且会再次加锁
pthread_cond_wait(&_cond, &lock);
-
条件信号
-
通知一条wait所在线程.
pthread_cond_signal(&_cond);
-
条件通知
-
通知所有的线程中所有的wait阻塞
pthread_cond_broadcast(&_cond);
递归锁概念:
允许同一个线程对同一把锁进行多次加锁.
NSLock、NSRecursiveLock
NSLock 就是对普通的pthread_mutex的封装,所以是互斥锁.
NSRecursiveLock 是对递归类型的 phread_mutex的封装.
NSLock、NSRecursiveLock 相关方法:
-
锁的初始化
NSLock *lock = [[NSLock alloc] init]; NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
-
加锁
[lock lock];
-
解锁
[lock unlock];
NSCondition
NSCondition 是对 pthread_mutex 和 pthread_cond 的 封装.
NSCondition 相关方法:
-
初始化
NSCondition *cond = [[NSCondition alloc] init];
-
加锁
[cond lock];
-
解锁
[cond unlock];
-
等待
- 等待过程, 会进行解锁
- 重新进入的时候,会再次加锁.
[cond wait];
-
发送信号
[cond signal];
-
发送通知
[cond broadcast];
NSConditionLock
NSConditionLock 是对 NSCondition 进一步的封装. 新增了内部条件值 condition
.
作用: 可用于顺序调用某些事件.
-
根据
condition
来进行加锁.- (void)lockWhenCondition:(NSInteger)condition;
-
根据
condition
来进行解锁.- (void)unlockWhenCondition:(NSInteger)condition;
disptch_semaphore
disptch_semaphore 是 GCD API 中的信号量
信号的初始值,可以用来控制线程并发访问 的最大数量.
相关函数如下所示.
-
初始化
// 设置最大并发数为 3 dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
-
设置等待
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
-
发送信号
dispatch_semaphore_signal(semaphore);
@sychronized
@sychronized 是一个递归锁, 是对pthread_mutex的一个封装. 但是性能比较差,官方不推荐使用.
使用方式
@sychronized(...) {
// do some thing
}
线程同步的方案
- OSSpinLock
- os_unfair_lock
- pthread_mutex
- NSLock, NSrecursiveLock
- NSCondition, NSConditionLock
- 串行队列 Serial -> DESPATCH_QUEUE_SERAIL
- 信号量 disptch_semaphore
- @sychronized
atomic
atomic 保证了属性的 set 、 get 方法的原子性操作,相当于在set和get方法内部增加了线程同步的锁.
但 atomic并不能保证 属性的使用是安全的.
iOS不常用的原因:
- 比较损耗性能.
IO 操作的读写安全
场景需求:
- 同一时间只能有一个线程进行写的操作.
- 同一时间可以有多个线程进行读的操作.
- 同一时间不能既有写的操作,又有读的操作.
方案一: pthread_rwlock
pthread_rwlock 等待锁的线程会进入休眠.
相关函数:
-
初始化锁.
pthread_rwlock_t rwlock; pthread_rwlock_init(&rwlock, NULL);
-
读加锁.
pthread_rwlock_rdlock(&rwlock);
-
读-尝试加锁.
pthread_rwlock_tryrdlock(&rwlock);
-
写加锁.
pthread_rwlock_wrlock(&rwlock);
-
写-尝试加锁.
pthread_rwlock_trywrlock(&rwlock);
-
解锁.
pthread_rwlock_unlock(&rwlock);
-
销毁锁.
pthread_rwlock_destory(&rwlock);
方案二: dispatch_barrier_async 栅栏函数
栅栏函数实现读写锁的话,需要自己通过 dispatch_queue_create 创建一个并发队列.
问题: 下面GCD代码会打印什么? 为什么?
- (void)test {
NSLog(@"0");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"1");
});
NSLog(@"2");
}
打印 0,然后崩溃.原因是死锁造成.
主队列是一个串行队列, 往主队列中添加同步任务,导致任务互等,最终导致死锁.
问题: 下面GCD代码会打印什么? 为什么?
- (void)test {
dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 20; i++) {
dispatch_sync(queue, ^{
NSLog(@"%d - %@", i, [NSThread currentThread]);
});
}
}
会顺序打印,并且当前在主线程,那么就会打印出主线程.
串行队列添加同步任务,先进先出. 串行执行先进先出,所以会依次打印 0 - 19 数字.
问题: 下面GCD代码会打印什么? 为什么?
- (void)test {
NSLog(@"0");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"1 - %@", [NSThread currentThread]);
});
NSLog(@"2 - %@", [NSThread currentThread]);
}
打印顺序为 0 → 2 → 1, 打印线程为同一线程.
串行队列添加异步任务,不会创建新的线程.所以当前是什么线程,那么block中代码执行所在的线程就是什么线程.
添加异步任务,虽然是串行队列,但是需要等待打印完成Log2才能打印Log1.
问题: 下面GCD代码会打印什么? 为什么?
- (void)test {
for (int i = 0; i < 20; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%d - %@", i, [NSThread currentThread]);
});
}
NSLog(@"End");
}
先打印End,再随机打印 0 - 19; 打印的线程也不是同一个,是 20 个不同的线程.
添加异步任务,所以会执行完当前的任务,也就是打印End,再随机打印0 - 19.
全局队列(并发队列) 与 添加异步任务 的结合会导致新的线程开启.所以每一个任务都会在不同的线程进行执行.
问题: 下面GCD代码会打印什么? 为什么?
- (void)test {
dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 20; i++) {
dispatch_async(queue, ^{
NSLog(@"%d - %@", i, [NSThread currentThread]);
});
}
NSLog(@"End");
}
由于是并发队列,添加异步任务. 所以与上一题的结果分析一致.这里就不过多叙述了.
先打印End,再随机打印 0 - 19; 打印的线程也不是同一个,是 20 个不同的线程.
问题: 下面GCD代码会打印什么? 为什么?
- (void)test {
for (int i = 0; i < 20; i++) {
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%d - %@", i, [NSThread currentThread]);
});
}
NSLog(@"End");
}
先依次打印 0 - 19,再打印End; 不会创建新的线程,所以所有的线程都是当前的线程.
全局队列(并发队列) 与 同步添加任务,不会创建新的线程;并且在同一线程里面执行的话,那么就是顺序执行.
问题: 下面GCD代码会打印什么? 为什么?
- (void)test {
dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
大概率打印顺序为 1 → 5 → 2 → 3 → 4. 或者 1 → 2 → 3 → 4 → 5.
并发队列 + 添加异步任务 会导致创建新的线程. 当前线程与新线程的执行顺序未知, 所以大概率会先执行 1 → 5 .
在新的线程中,同步添加任务打印3,那么不会再次创建新的线程了,只会在当前线程串行执行. 也就是 2 → 3 → 4.
问题: 下面GCD代码会打印什么? 为什么?
- (void)test {
dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
并发队列 + 异步 嵌套 异步,那么会出现以下几种情况.
1 → 5 → 2 → 3 → 4
1 → 5 → 2 → 4 → 3
1 → 2 → 3 → 4 → 5
1 → 2 → 4 → 3 → 5
大概率为 1 → 5 → 2 → 4 → 3
并发队列 与 添加异步任务会创建新的线程, 新的线程就会导致顺序的不一致.
但是创建线程需要时间,所以大概率的打印顺序为 1 → 5 → 2 → 4 → 3
问题: 下面GCD代码会打印什么? 为什么?
- (void)test {
dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 20; i++) {
dispatch_async(queue, ^{
NSLog(@"第一 %d - %@", i, [NSThread currentThread]);
dispatch_barrier_async(queue, ^{
NSLog(@"第二 %d - %@", i, [NSThread currentThread]);
});
NSLog(@"第三 %d - %@", i, [NSThread currentThread]);
});
}
}
混合打印所有的 "第一 ..." 和 "第三... ", 顺序随机, 然后打印所有的 "第二 ....", 顺序随机. 所有的 "第二 ...." 打印的线程都是在同一个线程中.
使用栅栏异步函数,并不会阻塞线程,再添加完成异步任务就会直接打印 "第三... ".
问题: 下面GCD代码会打印什么? 为什么?
- (void)test {
dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"1");
});
dispatch_async(queue, ^{
sleep(1);
NSLog(@"2");
});
dispatch_barrier_sync(queue, ^{
NSLog(@"----%@-----",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
}
打印顺序为 1 → 2 → (栅栏函数打印线程) → (3 4), 大概率为 1 → 2 → (栅栏函数打印线程) → 4 → 3
栅栏同步函数 + 并发队列 会真正导致线程的阻塞, 那么 打印2
操作内部有延时,也会等待完成之后再执行栅栏同步函数的Block, 打印3
会创建新的线程.有一定的延时性, 所以大概率的顺序是 4 → 3.
问题: 下面GCD代码会打印什么? 为什么?
- (void)test {
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
dispatch_async(globalQueue, ^{
NSLog(@"1");
});
dispatch_async(globalQueue, ^{
sleep(1);
NSLog(@"2");
});
dispatch_barrier_sync(globalQueue, ^{
NSLog(@"----%@-----",[NSThread currentThread]);
});
dispatch_async(globalQueue, ^{
NSLog(@"3");
});
NSLog(@"4");
}
打印顺序为随机.
栅栏同步函数 + 全局并发队列,这时候栅栏函数相当于是dispatch_async,所以打印顺序为随机顺序.
问题: 下面GCD代码会打印什么? 为什么?
- (void)test {
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(globalQueue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
sleep(1);
NSLog(@"1");
});
dispatch_async(globalQueue, ^{
dispatch_semaphore_signal(semaphore);
NSLog(@"2");
});
dispatch_async(globalQueue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"3");
});
NSLog(@"4");
}
打印顺序大概率为 4 → 2 → 1, 3不会打印.
信号量 与 全局队列(并发队列),那么会 打印1
与 打印3
都会进入等待.
执行完 打印2
之后,往信号量发送信号, 打印1
与 打印3
会根据添加顺序进行执行,大概率打印1
先进先出先执行, 所以 打印3
没有机会执行.
添加异步任务,会在当前函数执行完再执行,所以会先 打印4
.
问题: 下面GCD代码会打印什么? 为什么?
- (void)test {
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(globalQueue, ^{
sleep(10);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"1");
});
dispatch_async(globalQueue, ^{
NSLog(@"2");
dispatch_semaphore_signal(semaphore);
});
dispatch_async(globalQueue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"3");
dispatch_semaphore_signal(semaphore);
});
dispatch_async(globalQueue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"4");
dispatch_semaphore_signal(semaphore);
});
}
打印顺序大概率为 2 → (4 , 3 随机顺序) → 1.
信号量 与 全局队列(并发队列),打印2
会先进入,然后打印完成之后发送信息.
打印3
打印4
都有等待与发送信息逻辑代码, 大概率会是 打印3
先于 打印4
进入等待状态,所以一般为先 打印3
再 打印4
.
打印1
则刚进入线程就休眠 10 秒, 10秒之后很大概率已经满足信号量条件所以会执行 打印1
.
问题: 下面GCD代码会打印什么? 为什么?
- (void)test {
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL);
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 等待
sleep(2);
NSLog(@"1");
});
dispatch_async(queue1, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
sleep(2);
NSLog(@"2");
});
dispatch_async(queue1, ^{
sleep(2);
NSLog(@"3");
dispatch_semaphore_signal(sem);
});
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
sleep(2);
NSLog(@"4");
dispatch_semaphore_signal(sem);
});
}
结果为 无输出信息
打印2
打印3
在串行队列中,所以先执行完打印2
, 再执行打印3
. 打印2
需要一直等待信息,信息的发送在 打印3
中.所以最终不能造成所有的流程都不能进行打印输出.
问题: 实现一个场景:图片异步加载,确保全部加载完成后,再使用,保证数据安全
- (void)download {
dispatch_group_t group = dispatch_group_create();
dispath_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
__weak typeof(self) weakSelf = self;
dispatch_group_async(group, queue, ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
//创建调度组
NSString *logoStr1 = @"https://img1.baidu.com/it/u=1313595101,2257100959&fm=253&fmt=auto&app=120&f=GIF?w=450&h=300";
NSData *data1 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr1]];
UIImage *image1 = [UIImage imageWithData:data1];
dispatch_barrier_async(queue, ^{
[strongSelf.mArray addObject:image1];
});
});
dispatch_group_async(group, queue, ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
//创建调度组
NSString *logoStr2 = @"https://img1.baidu.com/it/u=1313595101,2257100959&fm=253&fmt=auto&app=120&f=GIF?w=450&h=300";
NSData *data2 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr2]];
UIImage *image2 = [UIImage imageWithData:data2];
dispatch_barrier_async(queue, ^{
[strongSelf.mArray addObject:image2];
});
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
});
}
Comments | 0 条评论