多线程
iOS中的常见多线程方案
| 技术方案 | 简介 | 语言 | 线程生命周期 | 使用频率 |
|---|---|---|---|---|
| pthread | 一套通用的多线程API 适用于Unix\Linux\Windows等系统 跨平台\可移植 使用难度大 | C | 程序员管理 | 几乎不用 |
| NSThread | 使用更加面向对象 简单易用,可直接操作线程对象 | OC | 程序员管理 | 偶尔使用 |
| GCD | 替代NSThread等线程技术 | C | 自动管理 | 经常使用 |
| NSOperation | 基于GCD(底层是GCD) | OC | 自动管理 | 经常使用 |
GCD的常用函数
GCD中有2个用来执行任务的函数
//创建异步任务
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"执行任务-%@", [NSThread currentThread]);
});
//创建同步任务
dispatch_sync(queue, ^{
NSLog(@"执行任务-%@", [NSThread currentThread]);
});
//打印
//执行任务-<_NSMainThread: 0x60000077c040>{number = 1, name = main}
//执行任务-<_NSMainThread: 0x60000077c040>{number = 6, name = main}
GCD的队列
GCD的队列可以分为2大类型:
并发队列:
可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
并发功能只有在异步(dispatch_async)函数下才有效
串行队列:
让任务一个接着一个地执行
容易混淆的术语
有4个术语比较容易混淆:同步、异步、并发、串行
同步和异步主要影响: 能不能开启新的线程
同步:在当前线程中执行任务,不具备开启新线程的能力
异步:在新的线程中执行任务,具备开启新线程的能力
并发和串行主要影响:任务的执行方式
并发:多个任务并发(同时)执行
串行:一个任务执行完毕后,再执行下一个任务
各种队列的执行效果
| 并发队列 | 手动串行队列 | 主队列 | |
|---|---|---|---|
| 同步(sync) | 没有开启新的线程 串行执行任务 | 没有开启新的线程 串行执行任务 | 没有开启新的线程 串行执行任务 |
| 异步(async) | 有开启新线程 并发执行任务 | 有开启新线程 串行执行任务 | 没有开启新的线程 串行执行任务 |
死锁问题
//会产生死锁
//因为下面的是全局同步任务
//在执行同步任务之前,是需要去结束上一个任务
//而上一个任务是viewDidLoad。是还没有结束的,所以这个任务也不会去执行,导致了死锁。
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"2");
});
NSLog(@"3");
}
//是否会死锁
-(void)interview03{
NSLog(@"1");
//串行队列
dispatch_queue_t queue = dispatch_queue_create("myQueu", DISPATCH_QUEUE_SERIAL);
//异步
dispatch_async(queue, ^{
NSLog(@"2");
//同步 会死锁
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
//是否会死锁 不会
-(void)interview04{
NSLog(@"1");
dispatch_queue_t queue = dispatch_queue_create("myQueu", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
死锁规律:使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)。也就是说,sync任务所在的线程如果是串行就会导致。
在子线程中定时器不启动面试题
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:0.0];
NSLog(@"3");
});
}
-(void)test{
NSLog(@"2");
}
全局并发队列,子线程中执行一个几秒后的操作,打印结果:
2022-06-24 10:02:42.306969+0800 多线程[43027:644829] 1
2022-06-24 10:02:42.307178+0800 多线程[43027:644829] 3
2没有被打印。
原因是子线程不会主动去添加RunLoop,所以定时器的任务不会被执行。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:0.0];
NSLog(@"3");
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
});
}
-(void)test{
NSLog(@"2");
}
添加RunLoop后,2被打印。结果是:
2022-06-24 10:08:42.851329+0800 多线程[43309:652496] 1
2022-06-24 10:08:42.851805+0800 多线程[43309:652496] 3
2022-06-24 10:08:42.852029+0800 多线程[43309:652496] 2
如果不添加延迟
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil];
NSLog(@"3");
});
}
-(void)test{
NSLog(@"2");
}
立即执行,打印结果为:
2022-06-24 10:10:33.652839+0800 多线程[43389:655004] 1
2022-06-24 10:10:33.653042+0800 多线程[43389:655004] 2
2022-06-24 10:10:33.653197+0800 多线程[43389:655004] 3
并发队列
使用并发队列。先异步执行2个任务,完成后在异步执行一个任务。
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 创建并发队列
dispatch_queue_t queue = dispatch_queue_create("my_queue", DISPATCH_QUEUE_CONCURRENT);
// 添加异步任务
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务1-%@", [NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务2-%@", [NSThread currentThread]);
}
});
// 等前面的任务执行完毕后,会自动执行这个任务
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务3-%@", [NSThread currentThread]);
}
});
//打印结果
2022-06-24 11:53:22.587432+0800 多线程[47792:742165] 任务2-<NSThread: 0x600003d14b00>{number = 4, name = (null)}
2022-06-24 11:53:22.587431+0800 多线程[47792:742164] 任务1-<NSThread: 0x600003d1fe80>{number = 6, name = (null)}
2022-06-24 11:53:22.587619+0800 多线程[47792:742165] 任务2-<NSThread: 0x600003d14b00>{number = 4, name = (null)}
2022-06-24 11:53:22.587621+0800 多线程[47792:742164] 任务1-<NSThread: 0x600003d1fe80>{number = 6, name = (null)}
2022-06-24 11:53:22.587688+0800 多线程[47792:742165] 任务2-<NSThread: 0x600003d14b00>{number = 4, name = (null)}
2022-06-24 11:53:22.587712+0800 多线程[47792:742164] 任务1-<NSThread: 0x600003d1fe80>{number = 6, name = (null)}
2022-06-24 11:53:22.587760+0800 多线程[47792:742165] 任务2-<NSThread: 0x600003d14b00>{number = 4, name = (null)}
2022-06-24 11:53:22.587792+0800 多线程[47792:742164] 任务1-<NSThread: 0x600003d1fe80>{number = 6, name = (null)}
2022-06-24 11:53:22.587825+0800 多线程[47792:742165] 任务2-<NSThread: 0x600003d14b00>{number = 4, name = (null)}
2022-06-24 11:53:22.587855+0800 多线程[47792:742164] 任务1-<NSThread: 0x600003d1fe80>{number = 6, name = (null)}
2022-06-24 11:53:22.588034+0800 多线程[47792:742164] 任务3-<NSThread: 0x600003d1fe80>{number = 6, name = (null)}
2022-06-24 11:53:22.588334+0800 多线程[47792:742164] 任务3-<NSThread: 0x600003d1fe80>{number = 6, name = (null)}
2022-06-24 11:53:22.588408+0800 多线程[47792:742164] 任务3-<NSThread: 0x600003d1fe80>{number = 6, name = (null)}
2022-06-24 11:53:22.588468+0800 多线程[47792:742164] 任务3-<NSThread: 0x600003d1fe80>{number = 6, name = (null)}
2022-06-24 11:53:22.588529+0800 多线程[47792:742164] 任务3-<NSThread: 0x600003d1fe80>{number = 6, name = (null)}
回到主线程执行:
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 创建并发队列
dispatch_queue_t queue = dispatch_queue_create("my_queue", DISPATCH_QUEUE_CONCURRENT);
// 添加异步任务
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务1-%@", [NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务2-%@", [NSThread currentThread]);
}
});
// 回主线程执行
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务3-%@", [NSThread currentThread]);
}
});
多线程的安全隐患
资源共享:
1块资源可能会被多个线程共享、也就是多个线程可能会访问同一块资源。
比如多个线程访问同一个对象、同一个变量、同一个文件。
当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。
多线程安全隐患的解决方案
解决方案:使用线程同步技术(同步、就是协同步调,按预定的先后次序进行)
常见的线程同步技术是: 加锁
iOS中的线程同步方案
OSSpinLock
os_unfair_lock
pthread_mutex
dispatch_semaphore
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSRecursiveLock
NSCondition
NSConditionLock
@synchronized
OSSpinLock
-
OSSpinLock 叫做 ”自旋锁“,等待锁的线程会处于忙等状态,一直占用CPU资源。
-
目前已经不再安全,可能会出现优先级反转问题。
-
如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁。
-
需要导入头文件 #import <libkern/OSAtomic.h>
//初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
//尝试加锁
bool result = OSSpinLockTry(&lock)
//加锁
OSSpinLockLock(&lock);
//解锁
OSSpinLockUnlock(&lock);
os_unfair_lock
-
os_unfair_lock 用于取代不安全的OSSpinLock, 从iOS10开始才支持
-
从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等
-
需要导入头文件 #import <os/lock.h>
//初始化
os_unfair_lock moneyLock = OS_UNFAIR_LOCK_INIT;
//尝试加锁
os_unfair_lock_trylock(&lock);
//加锁
os_unfair_lock_lock(&lock);
//解锁
os_unfair_lock_unlock(&lock);
pthread_mutex
-
mutex叫做”互斥锁“,等待锁的线程会处于休眠状态
-
需要导入头文件 #import <pthread.h>
//初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
//有不同模式
//PTHREAD_MUTEX_DEFAULT 默认
//PTHREAD_MUTEX_RECURSIVE 递归锁
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
//初始化锁
pthread_mutex_init(mutex, &attr);
//销毁属性
pthread_mutexattr_destroy(&attr);
//加锁
pthread_mutex_lock(&mutex);
//解说
pthread_mutex_unlock(&mutex);
//销毁锁
pthread_mutex_destroy(&mutex);
pthread_mutex – 条件 生产消费模式
-(instancetype)init{
if (self = [super init]) {
//初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
//递归锁
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
//初始化锁
pthread_mutex_init(&_mutex, &attr);
//销毁属性
pthread_mutexattr_destroy(&attr);
self.data = [NSMutableArray array];
pthread_cond_init(&_condition, NULL);
}
return self;
}
-(void)otherTest{
[[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
[[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}
// 删除数组中的元素
-(void)__remove{
pthread_mutex_lock(&_mutex);
if (self.data.count == 0) {
// 等待 解锁
// 向下执行 并 加锁
pthread_cond_wait(&_condition, &_mutex);
}
[self.data removeLastObject];
NSLog(@"删除了元素");
pthread_mutex_unlock(&_mutex);
NSLog(@"2");
}
// 往数组中添加元素
-(void)__add{
pthread_mutex_lock(&_mutex);
[self.data addObject:@"Test"];
NSLog(@"添加了元素");
// 通知等待的线程
pthread_cond_signal(&_condition);
//广播 解开所有等待的锁
pthread_cond_broadcast(&_condition);
pthread_mutex_unlock(&_mutex);
NSLog(@"1");
}
- (void)dealloc{
pthread_mutex_destroy(&_mutex);
}
NSLock
NSLock 是 pthread_mutex 的封装,看实现用到了 pthread_mutex 的代码。
@interface NSLockDemo()
@property (strong, nonatomic) NSLock *moneyLock;
@property (strong, nonatomic) NSLock *ticketLock;
@end
@implementation NSLockDemo
- (instancetype)init
{
if (self = [super init]) {
self.moneyLock = [[NSLock alloc] init];
self.ticketLock = [[NSLock alloc] init];
}
return self;
}
- (void)__drawMoney
{
[self.moneyLock lock];
[super __drawMoney];
[self.moneyLock unlock];
}
NSCondition
// NSCondition是对mutex和cond的封装
// 而且遵守了NSLocking协议
@interface NSCondition : NSObject <NSLocking> {
@private
void *_priv;
}
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
@interface NSConditionDemo ()
@property (strong, nonatomic) NSCondition *condition;
@property (strong, nonatomic) NSMutableArray *data;
@end
@implementation NSConditionDemo
-(instancetype)init{
if (self = [super init]) {
self.condition = [[NSCondition alloc] init];
self.data = [NSMutableArray array];
}
return self;
}
-(void)otherTest{
[[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
[[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}
// 删除数组中的元素
-(void)__remove{
[self.condition lock];
if (self.data.count == 0) {
[self.condition wait];
}
[self.data removeLastObject];
NSLog(@"删除了元素");
[self.condition unlock];
NSLog(@"2");
}
// 往数组中添加元素
-(void)__add{
[self.condition lock];
[self.data addObject:@"Test"];
NSLog(@"添加了元素");
[self.condition signal];
//广播
// [self.condition broadcast];
[self.condition unlock];
NSLog(@"1");
}