你理解的多线程有什么?
多线程中涉及到同步、异步、串行、并发。
同步、异步主要影响是否能开启新的线程
同步:在当前线程中执行任务,不具备开启新的线程的能力
异步:在新的线程中执行任务,具备开启新的线程的能力
并发、串行主要影响任务的执行方式
并发:多个任务并发(同时)执行
串行:一个任务执行完毕后在执行下一个任务
iOS的多线程方案有哪几种,你更倾向于哪个?
pthread
简介:一套通用的多线程API,适用于Linux/Unix/Windows等系统,跨平台/可移植,使用难度大。
使用的语言:C语言
线程生命周期:程序员管理
使用频率:几乎不用
NSThread
简介:使用更加面向对象,简单易用,可直接操作线程对象。
使用的语言:OC语言
线程生命周期:程序员管理
使用频率:偶尔使用
GCD
简介:旨在替代NSThread等线程技术,充分利用设备的多核。
使用的语言:C语言
线程生命周期:自动管理
使用频率:经常使用
NSOperation
简介:基于GCD(底层是GCD),比GCD多了一些简单实用的功能,使用更加面向对象。
使用的语言:OC语言
线程生命周期:自动管理
使用频率:经常使用
你项目中有用到过GCD吗?
GCD 的使用步骤:
1.创建一个任务队列(串行队列、并行队列)
//参数1代表队列的唯一标识符,可为空
//参数2用来设置为串行队列还是并发队列
//DISPATCH_QUEUE_CONCURRENT:并发队列
//DISPATCH_QUEUE_SERIAL:串行队列
dispatch_queue_t queue = dispatch_queue_create("queue",DISPATCH_QUEUE_CONCURRENT);
2.将任务添加到创建的等待队列并指定任务的执行类型(同步、异步)
//异步
dispatch_async(queue, ^{
//执行的任务代码
});
//同步
dispatch_sync(queue1, ^{
//执行的任务代码
});
GCD 队列组
dispatch_group
队列组
在多线程的操作中 有时候有多个任务异步去执行 执行完后想统一处理某些事情 就可以考虑用队列组。
//创建队列组
dispatch_group_t group = dispatch_group_create();
//创建并发队列
dispatch_queue_t queue = dispatch_queue_create("myQueue", 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, ^{
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = 0; i < 10; i ++) {
NSLog(@"执行任务3=%@",[NSThread currentThread]);
}
});
});
dispatch_group_notify
:当任务管理组中的任务都已经执行完了会通知这个函数执行;
dispatch_group_enter
:使任务管理组里面的任务数加1;
dispatch_group_leave
:使任务管理组里面的任务数减1;
这三个方法必须在同一个任务队列中,dispatch_group_notify才会执行。
如果使用上面两个函数,那么只有在任务管理组中的dispatch_group_enter和dispatch_group_leave都平衡的情况下dispatch_group_notify才会执行。
NSOperation
NSOperation 是苹果公司对 GCD 的封装,完全面向对象,所以使用起来更好理解。
NSOperation 和 NSOperationQueue 分别对应 GCD 的 任务 和 队列 。
操作步骤:
1.将要执行的任务封装到一个 NSOperation 对象中。
2.将此任务添加到一个 NSOperationQueue 对象中。
3.然后系统就会自动在执行任务。
添加任务
NSOperation 只是一个抽象类,所以不能封装任务。
但他有两个子类用于封装任务:NSInvocationOperation
和NSBlockOperation
。
创建一个Operation后,需要调用start方法开启任务,默认在当前队列中同步执行,中途取消任务的话只需调用cancel 方法。
添加任务:
- (void)operationTest {
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
[operation start];
// [operation cancel];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"--%@",[NSThread currentThread]);
}];
//给operation1添加多个Block,这样 Operation 中的任务 会并发执行,它会 在主线程和其它的多个线程 执行这些任务。
for (int i = 0; i < 5; i ++) {
[operation1 addExecutionBlock:^{
NSLog(@"第%d次执行 %@",i,[NSThread currentThread]);
}];
}
[operation1 start];
//NOTE:addExecutionBlock 方法必须在 start() 方法之前执行,否则就会报错
}
- (void)run {
NSLog(@"--%@",[NSThread currentThread]);
}
NSOperationQueue
调用一个 NSOperation 对象的 start() 方法来启动这个任务,但是这样默认是 同步执行 的。就算是 addExecutionBlock 方法,也会在 当前线程和其他线程 中执行,也就是说还是会占用当前线程。这是就要用到队列 NSOperationQueue 了。
按类型来说的话一共有两种类型:主队列、其他队列。
只要添加到队列,会自动调用任务的 start() 方法
主队列
这是一个特殊的线程,必须串行。所以添加到主队列的任务都会一个接一个地排着队在主线程处理。
NSOperationQueue *mineQueue = [NSOperationQueue mainQueue];
其他队列
因为主队列比较特殊,所以会单独有一个类方法来获得主队列。那么通过初始化产生的队列就是其他队列了。
注意:其他队列的任务会在其他线程并行执行。
创建队列:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//设置最大并发数,当设为1时,就是串行
queue.maxConcurrentOperationCount = 1;
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"-%@",[NSThread currentThread]);
}];
for (int i = 0; i < 5; i ++) {
[operation addExecutionBlock:^{
NSLog(@"1--第%d次执行 %@",i,[NSThread currentThread]);
}];
}
[queue addOperation:operation];
[queue addOperationWithBlock:^{
for (int i = 0; i < 5; i ++) {
NSLog(@"2--第%d次执行 %@",i,[NSThread currentThread]);
}
}];
NSOperation添加依赖
- (void)addDependency {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务1-%@",[NSThread currentThread]);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务2-%@",[NSThread currentThread]);
}];
for (int i = 0; i < 5; i ++) {
[operation2 addExecutionBlock:^{
NSLog(@"任务2--第%d次执行 %@",i,[NSThread currentThread]);
}];
}
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务3-%@",[NSThread currentThread]);
}];
[operation2 addDependency:operation1]; //任务2依赖任务1
[operation3 addDependency:operation2]; //任务3依赖任务2
// //解除依赖
// [operation3 removeDependency:operation2];
[queue addOperations:@[operation1,operation2,operation3] waitUntilFinished:NO];
}
注意:不能添加相互依赖,会死锁。
可以在不同的队列之间依赖,依赖是添加到任务上的,和队列没关系.
线程安全的处理手段有哪些?
加锁
OC你了解的锁有哪些?
OSSpinLock
自旋锁,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源。
目前已经不再安全,可能会出现优先级反转问题:
如果等待锁的线程优先级较高,它会一直占着CPU资源,优先级低的线程就无法释放锁。
OSSpinLock 的使用:
//需要导入头文件
#import <libkern/OSAtomic.h>
@interface ViewController ()
@property (nonatomic, assign)OSSpinLock lock;
@end
//初始化锁
_lock = OS_SPINLOCK_INIT;
//加锁
OSSpinLockLock(&_lock);
//解锁
OSSpinLockUnlock(&_lock);
//尝试加锁
if (OSSpinLockTry(&_lock)) {
//解锁
OSSpinLockUnlock(&_lock);
}
os_unfair_lock
用于取代不安全的OSSpinLock,从iOS10开始才支持
从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等。
使用:
//需要导入头文件
#import <os/lock.h>
//初始化
self.lock = OS_UNFAIR_LOCK_INIT;
//加锁
os_unfair_lock_lock(&_lock);
//解锁
os_unfair_lock_unlock(&_lock);
pthread_mutex
mutex 叫做互斥锁,等待锁的线程会处于休眠状态
使用:
//需要导入头文件
#import <pthread.h>
@property (nonatomic, assign)pthread_mutex_t mutex;
//初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
/*
PTHREAD_MUTEX_NORMAL //默认
PTHREAD_MUTEX_ERRORCHECK //检错锁 如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
PTHREAD_MUTEX_RECURSIVE //递归锁
//递归锁:允许同一个线程对一把锁进行重复加锁,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
*/
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
//初始化锁
pthread_mutex_init(&mutex, &attr);
//销毁属性
pthread_mutexattr_destroy(&attr);
//加锁
pthread_mutex_lock(&_mutex);
//解锁
pthread_mutex_unlock(&_mutex);
- (void)dealloc {
//销毁锁
pthread_mutex_destroy(&_ticketMutex);
pthread_mutex_destroy(&_moneyMutex);
}
//初始化条件
pthread_cond_t condition;
pthread_cond_init(&condition, NULL);
//等待条件(进入休眠,放开mutex锁,被唤醒后会再次对mutex加锁)
pthread_cond_wait(&condition, &_moneyMutex);
//激活一个等待该条件的线程
pthread_cond_signal(&condition);
//激活所有等待该条件的线程
pthread_cond_broadcast(&condition);
//销毁资源
pthread_cond_destroy(&condition);
dispatch_semaphore
semaphore叫做信号量。
信号量的初始值,可以用来控制线程并发访问的最大数量
使用:
@property (nonatomic ,strong) dispatch_semaphore_t semaphore;
//设置最大并发数量
self.semaphore = dispatch_semaphore_create(5);
//如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
//如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
//让信号量的值+1
dispatch_semaphore_signal(self.semaphore);
dispatch_queue(DISPATCH_QUEUE_SERIAL)
直接使用GCD的串行队列,也可以实现线程同步的。
使用:
@property (nonatomic, strong) dispatch_queue_t ticketQueue;
//初始化串行队列
self.ticketQueue = dispatch_queue_create("ticketQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(self.ticketQueue, ^{
//执行操作
});
NSLock
NSLock是对mutex普通锁的封装。
使用:
//初始化
NSLock *ticketLock = [[NSLock alloc] init];
//加锁
[self.ticketLock lock];
//解锁
[self.ticketLock unlock];
NSRecursiveLock
NSRecursiveLock是对mutex递归锁的封装。API跟NSLock基本上一致。
NSCondition
是对mutex和cond的封装。
使用:
@property (nonatomic, strong) NSCondition *condition;
//初始化
self.condition = [[NSCondition alloc] init];
//加锁
[self.condition lock];
//解锁
[self.condition unlock];
//信号
[self.condition signal];
//广播
[self.condition broadcast];
NSConditionLock
是对NSCondition的进一步封装,可以设置具体的条件值。
使用:
@interface NSConditionDemo ()
@property (nonatomic, strong) NSConditionLock *conditionLock;
@end
@implementation NSConditionDemo
- (instancetype)init {
if (self = [super init]) {
//初始化锁并添加条件值1
self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
}
return self;
}
- (void)otherTest {
[[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
[[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
[[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start];
}
- (void)__one {
//根据条件值加锁
[self.conditionLock lockWhenCondition:1];
NSLog(@"__one");
//根据条件值解锁
[self.conditionLock unlockWithCondition:2];
}
- (void)__two {
[self.conditionLock lockWhenCondition:2];
NSLog(@"__two");
[self.conditionLock unlockWithCondition:3];
}
- (void)__three {
[self.conditionLock lockWhenCondition:3];
NSLog(@"__three");
//解锁
[self.conditionLock unlock];
}
@end
@synchronized
@synchronized是对mutex递归锁的封装。
使用:
@synchronized (self) {
//执行操作
}
自旋锁、互斥锁的比较
什么情况下使用自旋锁比较划算:
- 预计线程等待锁的时间很短
- 加锁的代码(临界区)经常被调用,但竞争情况很少发生
- CPU资源不紧张
- 多核处理器
什么情况用互斥锁比较划算:
- 预计线程等待锁的时间较长
- 单核处理器
- 临界区有IO操作
- 临界区代码复杂或者循环量大
- 临界区竞争非常激烈
atomic和nonatomic
atomic:原子性
是默认的,给属性加上 atomic 修饰,可以保证属性的setter和getter都是原子性操作,相当于在getter和setter内部加了线程同步的锁,也就是保证setter和getter内部是线程同步的。
使用atomic并不能保证绝对的线程安全,对于要绝对保证线程安全的操作,还需要使用更高级的方式来处理,比如NSSpinLock、@syncronized等
nonatomic :非原子性
不是默认的,nonatomic修饰的属性,不做保持getter完整性保证,但在运行速度上要比atomic快。
iOS中的读写安全方案
思考如何实现以下场景:
- 同一时间,只能有1个线程进行写的操作;
- 同一时间,允许有多个线程进行读的操作;
- 同一时间,不允许既有写的操作,又有读的操作。
上面的场景就是经典的“多读单写”,经常用于文件数据的读写操作,iOS中的实现方案有:
pthread_rwlock:读写锁
dispatxh_barrier_async:异步栅栏调用
pthread_rwlock
使用:
//导入头文件
#import <pthread/pthread.h>
@property (nonatomic ,assign) pthread_rwlock_t lock;
//初始化锁
pthread_rwlock_init(&_lock,nil);
- (void)read {
pthread_rwlock_rdlock(&_lock);
sleep(1);
NSLog(@"--%s--",__func__);
pthread_rwlock_unlock(&_lock);
}
- (void)write {
pthread_rwlock_wrlock(&_lock);
sleep(1);
NSLog(@"--%s--",__func__);
pthread_rwlock_unlock(&_lock);
}
- (void)dealloc {
//销毁锁
pthread_rwlock_destroy(&_lock);
}
dispatxh_barrier_async
这个函数传入的并发队列必须是自己通过dispatch_queue_create
创建的
如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async
函数的效果
使用:
dispatch_queue_t queue = dispatch_queue_create("rw_queue",DISPATCH_QUEUE_CONCURRENT);
//读
dispatch_async(queue, ^{
});
//写
dispatch_barrier_async(queue, ^{
});