多线程
1. GCD
- GCD中没有线程保活,线程保活就是通过RunLoop来实现的
- GCD只负责开启线程执行任务,不负责线程保活
1.1 IOS中常见的多线程方案
pthread
:一套通用的多线程API,适用于Unix/Linux/Windows等系统,是跨平台、可移植,使用难度大,是C语言,该线程的声明周期有程序员管理,几乎不会使用NSThread
: 使用更加面向对象,使用简单,可以直接操作线程对象,声明周期由程序员自己管理,偶尔会使用,就是对pthread的一层封装GCD
: 代替NSThread
等多线程技术,充分利用设备的多核,C语言,线程的生命周期是自动管理的,使用频率非常高NSOperation
: 基于GCD的封装,比GCD多了一些更简单实用的功能,使用更加面向对象,线程的生命周期是自动管理,使用频率也非常高
1.2 GCD的常用函数
- GCD 中有2个常用来执行任务的函数:
- 用同步的方式执行任务:
dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
- queue:队列
- block:任务
- 用异步的方式执行任务:
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
- 用同步的方式执行任务:
- CGD是开源的:GCD源码地址
1.3 GCD的队列
-
GCD的队列可以分为2大类型
- 并发队列(Concurrent Dispatch Queue)
- 可以让多个任务**并发(同时)**执行(自动开启多个线层同时执行任务)
- 并发功能只有在异步(
dispatch_async
)函数下才有效
- 串行队列(Serial Diaptch Queue)
- 让任务一个接一个的执行(一个任务执行完毕之后,在执行下一个任务)
- 并发队列(Concurrent Dispatch Queue)
-
比较容易混淆的4个术语: 同步、异步、
并发
、串行
-
同步线程和异步线程主要影响:
能不能开始新的线程
- 同步:在
当前
线程中执行任务,不具备
开启新的线程能力 - 异步:在
新线程
中执行任务,具备
开启新线程的能力
- 同步:在
-
并发
和串行
主要影响: 任务的执行方式- 并发:
多个
任务并发(同时)执行 - 串行:
一个
任务执行完毕后,在执行下一个任务
- 并发:
-
-
dispatch_sync
:在当前线程执行,并且是立刻在当前线程中执行任务,执行完毕之后才能往下执行 -
dispatch_async
:只是具备开线程的能力,不代表使用就一定能开启新线程,例如:在主队列下就是在主线程执行 -
针对异同步(
sync
)和异步(async
)会不会开启新的线程,总结如下图:
1.4 GCD内线程死锁问题
- 使用
sync
(同步)函数往当前串行队列中添加任务,会卡主当前的串行队列(产生死锁)
1.4.1 同步队列添加同步任务
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"执行任务1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"执行任务2");
});
NSLog(@"执行任务3");
}
- 上述代码会产生死锁
- 首先
dispatch_sync
表示在立刻当前线程执行,而且又是在主队列,所以是在主线程执行的,首先我们认为主队列中已经有一个任务(viewDidLoad函数
),然后又添加了一个dispatch_sync任务
,队列是先进先出的,所以viewDidLoad
函数必须要执行完成,才能执行dispatch_sync
任务,但是想要执行完viewDidLoad
函数,又必须先执行完dispatch_sync
任务,所以这两个任务在互相等待, 最终产生死锁
1.4.2 同步队列添加异步任务
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"执行任务1");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"执行任务2");
});
NSLog(@"执行任务3");
}
- 上述代码不会产生死锁
- 因为
dispatch_async
不要求立刻当前线程执行任务,可以等上一个任务执行完成,在执行这个任务,所以不存在互相等待问题,所以不会产生死锁
1.4.3 同步队列异步任务添加同步任务
- (void)viewDidLoad {
[super viewDidLoad];
//创建一个同步队列
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"执行任务1");
dispatch_async(queue, ^{//block0
NSLog(@"执行任务2");
dispatch_sync(queue, ^{//block1
NSLog(@"执行任务3");
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");
}
-
上述代码会产生死锁
-
打印结果:
-
执行完任务2之后程序崩溃,发生死锁问题,在同步队列中,block0对象需要等block1对象执行完成之后才能继续往下执行,而block1对象又必须要等block0对象执行完成,才能执行,所以相互等待,产生死锁
1.4.4 异步任务添加同步任务(不同队列添加)
- (void)viewDidLoad {
[super viewDidLoad];
//创建一个同步队列
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
// dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_SERIAL);
NSLog(@"执行任务1");
dispatch_async(queue, ^{
NSLog(@"执行任务2");
dispatch_sync(queue2, ^{
NSLog(@"执行任务3");
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");
}
- 打印结果:
1.4.5 异步任务添加同步任务(并发队列)
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{ // block0
NSLog(@"执行任务2");
dispatch_sync(queue, ^{ // block1
NSLog(@"执行任务3");
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");
- 队列是并发队列,允许并发执行,block0任务执行到block1任务时,block1会执行,然后再执行block0任务,所以不会产生死锁问题
- 打印结果:
1.5 performSelector: withObject:nil afterDelay:
- 看下列代码执行结果
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"1");
//[self performSelector:@selector(test)];
//底层是 转成objc_msgSend() 发送消息 可以起作用
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
NSLog(@"3");
});
}
- (void)test {
NSLog(@"2");
}
- 打印结果: 1、3
performSelector
函数底层是objc_msgSend()
,实现的,所有能够在子线程中执行performSelector: withObject: afterDelay:
: 带有延迟属性的函数,本质上是往该线程的RunLoop中添加一个定时器,而子线程的RunLoop对象是默认没有启动的,定时器是是没办法工作的,所以这个方法在子线程中是无效的- 如何解决这个问题, 我们可以把子线程的Runloop启动起来,这样这个方法就可以实现调用,启动Runloop代码如下:
//添加port这句代码可以去掉,因为上面代码底层默认添加了一个NSTimer对象到RunLoop对象了,所以这里也可以不添加port了,因为RunLoop只要有Source、timer、observer中任何一个就不会立刻退出,可以跑起来
//[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
- 在子线程中使用NSTime,必须要开启RunLoop,不然子线程执行完任务,生命周期结束了,就无法执行NSTimer了
1.6 group(GCD线程组)
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"异步队列1====%d",i);
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"异步队列2====%d",i);
}
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"两个队列都执行完了---");
});
- 等两个并发任务执行完成,再去执行某一个任务
- 还可以等两个并发任务执行完毕,在去执行另外两个并发任务,可以使用下面这种用法:
dispatch_group_notify(group, queue, ^{
NSLog(@"并发任务1---");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"并发任务2---");
});
1.7 GNUStep(仿Foundation框架开源源码)
- GNUstep是GNU技术的项目之一,他讲Cocoa的OC库重新开源实现了一遍
- 源码地址
- 虽然GNUstep不是苹果官方源码,但还具有一定的参考价值
1.8 多线程的安全隐患问题
1.8.1 概述和分析
-
资源共享
- 1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
- 比如多个线程访问同一个对象,同一个变量,同一个文件
-
当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题
-
安全隐患例子: 存钱取钱
-
安全隐患例子: 买票
-
安全隐患分析流程图:
-
解决方法: 使用线程同步技术(同步,就是协同步调,按预定的先后次序进行)
-
常见的线程同步技术:加锁
-
IOS中的线程同步方案:
OSSpinLok
os_unfair_lock
pthread_mutex
dispatch_semaphore
:信号量dispatch_queue(DISPATCH_QUEUE_SERIAL)
:串行队列NSLock
NSRecuresiveLock
NSCondition
NSConditionLock
@synchroized
1.8.2 OSSpinLock(自旋锁)
OSSpinLock
:自旋锁,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源- 目前已经不安全,可能会出现优先级反转的问题,
并且从IOS10以后不推荐大家使用,推荐大家使用os_unfair_lock
- 如果等待的线程优先级比较高,它会一直占用CPU的资源,优先级低的线程就无法释放锁
- 需要导入头文件:
#import <libkern/OSAtomic.h>
//初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
//尝试加锁 (如果需要等待就不加锁,直接返回false;如果不需要等待就加锁,返回true)
BOOL result = OSSpinLockTry(&lock);
//加锁
OSSpinLockLock(&lock);
//解锁
OSSpinLockUnlock(&lock);
1.8.3 os_unfair_lock(互斥锁)
os_unfair_lock
用于取代不安全的OSSpinLock
,从IOS10开始才支持- 从底层调用来看,等待
os_unfair_lock
锁的线程会处于休眠状态,并非忙等 - 需要导入头文件:
#import <os/lock.h>
//初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT
//尝试加锁 (该方法和OSSpinLock的OSSpinLockTry方法作用一样)
os_unfair_lock_trylock(&lock);
//加锁
os_unfair_lock_lock(&lock);
//解锁
os_unfair_lock_unlock(&lock);
1.8.4 pthread_mutex(跨平台)
mutex
:互斥锁,等待锁的线程会处于休眠状态- 需要导入头文件
#import <pthread/pthread.h>
- pthread_mutex:的锁的类型
PTHREAD_MUTEX_NORMAL
:普通锁PTHREAD_MUTEX_ERRORCHECK
PTHREAD_MUTEX_RECURSIVE
:递归锁,允许同一个线程对一把锁进行重复加锁,如果是不同线程是,那么效果跟上面加锁一样,只能等待这把锁解锁,才能再次进行加锁PTHREAD_MUTEX_DEFAULT
:默认类型的锁
1.8.4.1 默认类型
- 在初始化的时候 如果&attr(属性传空NULL 就是表示默认类型的锁)
- (pthread_mutex_t)__initlock:(pthread_mutex_t)lock {
//初始化锁的属性
pthread_mutexattr_t attrs;
pthread_mutexattr_init(&attrs);
/**
Mutex type attributes ,锁的类型属性
#define PTHREAD_MUTEX_NORMAL 0 普通锁
#define PTHREAD_MUTEX_ERRORCHECK 1
#define PTHREAD_MUTEX_RECURSIVE 2 ---》 递归锁
#define PTHREAD_MUTEX_DEFAULT
*/
//设置锁的类型
pthread_mutexattr_settype(&attrs, PTHREAD_MUTEX_NORMAL);
//初始化锁
pthread_mutex_init(&lock, &attrs);
//释放属性
pthread_mutexattr_destroy(&attrs);
//初始化的时候,如果属性参数传递NULL 代表是默认锁 类型 PTHREAD_MUTEX_DEFAULT
// pthread_mutex_init(&lock, NULL);
return lock;
}
//加锁
pthread_mutex_lock(&lock);
//尝试加锁
pthread_mutex_trylock(&lock)
//解锁
pthread_mutex_unlock(&lock );
//销毁锁
pthread_mutex_destroy(&lock);
1.8.4.2 递归锁
/// 测试递归锁: 对同一个线程是可以重复加锁的
- (void)otherTest {
pthread_mutex_lock(&_lock);
NSLog(@"__%s__",__func__);
static int count = 0;
if (count < 10) {
count ++;
[self otherTest];
}
pthread_mutex_unlock(&_lock);
}
1.8.4.3 pthread-mutex-条件锁
pthread_mutex
的条件,不一定需要通过unlock的方式解锁
@interface MutexDemo3()
@property (assign, nonatomic) pthread_mutex_t mutex;
@property (assign, nonatomic) pthread_cond_t cond;
@property (strong, nonatomic) NSMutableArray *data;
@end
@implementation MutexDemo3
- (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);
// 初始化条件
pthread_cond_init(&_cond, NULL);
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];
}
// 生产者-消费者模式
// 线程1
// 删除数组中的元素
- (void)__remove
{
pthread_mutex_lock(&_mutex);
NSLog(@"__remove - begin");
if (self.data.count == 0) {
// 等待条件(进入休眠,放开mutex锁;被唤醒后,会在此对mutex加锁)
pthread_cond_wait(&_cond, &_mutex);
}
[self.data removeLastObject];
NSLog(@"删除了元素");
pthread_mutex_unlock(&_mutex);
}
// 线程2
// 往数组中添加元素
- (void)__add
{
pthread_mutex_lock(&_mutex);
sleep(1);
[self.data addObject:@"Test"];
NSLog(@"添加了元素");
// 信号 激活一个等待该条件的线程
pthread_cond_signal(&_cond);
// 广播 激活所有等待该条件的线程
// pthread_cond_broadcast(&_cond);
pthread_mutex_unlock(&_mutex);
}
- (void)dealloc
{
//销毁资源
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
@end
- 该锁的条件应用的场景: 多线程之间的一个依赖问题的时候可以使用这种锁
1.8.5 NSLock、NSRecuresiveLock、NSCondition
- NSLock 是对mutex普通锁的封装
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
@interface NSLock : NSObject <NSLocking> {
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@end
//初始化锁
NSLock *lock = [[NSLock alloc] init];
- NSRecuresiveLock 也是对mutex递归锁的封装,API跟NSLock基本一致
- NSCondition是对mutex和cond(条件)的封装
@interface NSCondition : NSObject <NSLocking> {
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
@end
1.8.6 NSConditionLock
NSConditionLock
是基于NSCondition
的一个封装- API如下:
@interface NSConditionLock : NSObject <NSLocking> {
//初始化一个条件锁
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
@property (readonly) NSInteger condition;
//当条件满足的时候,在进行加锁,不然就一直在这里等待
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
// 修改该锁的条件,并且把当前锁放开(也就是解锁,并修改当前锁的条件值)
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
@end
- 通过加锁、解锁、修改条件来达到线程依赖的效果,可以控制哪条线程先执行,哪条线程后执行
@interface GYConditionLock()
@property (strong, nonatomic) NSConditionLock *lock;
@end
@implementation GYConditionLock
- (instancetype)init
{
if (self = [super init]) {
//[[NSConditionLock alloc] init]如果初始化条件, 默认condition为0
self.lock = [[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];
}
- (void)__one
{
//[self.lock lock]; -- 如果调用lock加锁方法不管条件是什么,都会加锁,该方法表示,只要没加锁,就加锁,跟条件没有任何关系
//当条件为1 时 加锁, 如果条件不是1 则一直等待,知道条件为1 在加锁
[self.lock lockWhenCondition:1];
NSLog(@"__one");
//修改加锁条件为2 ,并且放开当前锁(解锁)
[self.lock unlockWithCondition:2];
}
- (void)__two
{
[self.lock lockWhenCondition:2];
sleep(1);
NSLog(@"__two");
[self.lock unlock];
}
@end
1.8.7 dispatch_queue(DISPATCH_QUEUE_SERIAL)
- 直接使用GCD的串行队列,也可以实现线程同步的
- 线程同步的本质是不能同时让多条线程占用同一份资源,怎么样保证,按顺序访问,一次只有一条线程访问这份资源
@interface SerialQueueDemo ()
/** <#descrption#> */
@property (nonatomic, strong) dispatch_queue_t serialQueue;
@end
@implementation SerialQueueDemo
- (instancetype)init
{
if (self = [super init]) {
self.serialQueue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (void)__drawMoney
{
dispatch_sync(self.serialQueue, ^{
[super __drawMoney];
});
}
- (void)__saveMoney
{
dispatch_sync(self.serialQueue, ^{
[super __saveMoney];
});
}
- (void)__saleTicket
{
dispatch_sync(self.serialQueue, ^{
[super __saleTicket];
});
}
@end
1.8.8 dispatch_semaphore(信号量)
semaphore
:信号量- 信号量的初始值,可以用来控制线程并发访问的最大数量
- 信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步
- 参数方法介绍:
DISPATCH_TIME_FOREVER
: 表示一直等,等到信号量的值> 0DISPATCH_TIME_NOW
:表示当前马上判断这个信号量的值,如果不是>0, 那不等继续往下走,那这样就没办法达到线程同步的控制,会导致多条线程同时访问一个资源,所以一般是写DISPATCH_TIME_FOREVER
dispatch_semaphore_wait()
:如果信号量的值<=0,当前线程就会进入休眠等待(知道信号量的值>0);如果信号量的值>0。就减1,然后往下执行下面代码dispatch_semaphore_signal()
:让信号量的值加1
@interface GYSemaphoreDemo ()
/// 创建一个信号值
@property (nonatomic, strong)dispatch_semaphore_t semaphore;
/// 创建一个信号值
@property (nonatomic, strong)dispatch_semaphore_t moneySemaphore;
/// 创建一个信号值
@property (nonatomic, strong)dispatch_semaphore_t tickSemaphore;
@end
@implementation GYSemaphoreDemo
- (instancetype)init
{
self = [super init];
if (self) {
//初始化信号量,参数传递:控制最大并发数是 多少
self.semaphore = dispatch_semaphore_create(5);
self.moneySemaphore = dispatch_semaphore_create(1);
self.tickSemaphore = dispatch_semaphore_create(1);
}
return self;
}
//使用dispatch_semaphore 控制最多允许5条线程执行
- (void)otherTest {
for(int i = 0; i < 20 ; i++) {
[[[NSThread alloc] initWithTarget:self selector:@selector(threadTest) object:nil] start];
}
}
- (void)threadTest {
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
sleep(2);
NSLog(@"current thread------%@",[NSThread currentThread]);
dispatch_semaphore_signal(self.semaphore);
}
- (void)__drawMoney {
dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);
[super __drawMoney];
dispatch_semaphore_signal(self.moneySemaphore);
}
- (void)__saveMoney {
dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);
[super __saveMoney];
dispatch_semaphore_signal(self.moneySemaphore);
}
- (void)__saleTicket {
dispatch_semaphore_wait(self.tickSemaphore, DISPATCH_TIME_FOREVER);
[super __saleTicket];
dispatch_semaphore_signal(self.tickSemaphore);
}
@end
1.8.9 @synchronized
- @synchronized是对mutex递归锁的封装, 可以是最简单的一种方案
- 底层是对mutex的封装,底层是mutex的递归锁,支持对同一个线程重复加锁
- objc 源码
objc-sync.m
- (void)__drawMoney {
//[self class]: 该对象相当于一个锁对象
@synchronized ([self class]) { //底层执行 objc_sync_enter
[super __drawMoney];
}//objc_sync_exit
}
- (void)__saveMoney {
@synchronized ([self class]) {
[super __saveMoney];
}
}
- (void)__saleTicket {
//如果想每个方法单独使用一把锁,建议可以这样使用
static NSObject *objLock;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
objLock = [[NSObject alloc] init];
});
@synchronized (objLock) {
[super __saleTicket];
}
}
-
我们可以搭断点观察
@synchornized
底层执行方法
-
查看汇编代码:
-
上述两个方法源码:
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, ACQUIRE);
assert(data);
data->mutex.lock();
} else {
// @synchronized(nil) does nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil();
}
return result;
}
int objc_sync_exit(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, RELEASE);
if (!data) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
} else {
bool okay = data->mutex.tryUnlock();
if (!okay) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
}
}
} else {
// @synchronized(nil) does nothing
}
return result;
}
1.9 同步方案性能对比
- 所以建议大家使用
dispatch_semaphore
和pthread_mutex
方法来实现线程同步的操作
1.1.0 自旋锁、互斥锁比较
-
什么情况下使用自旋锁比较划算?
- 预计线程等待锁的时间很短
- 加锁的代码(临界区)经常被使用,但竞争情况很少发生
- CPU资源不紧张
- 多核处理器
-
什么情况下使用互斥锁比较划算?
- 预计线程等待时间比较长
- 单核处理器
- 临界区有IO操作
- 临界区代码复杂或则循环量大
1.11 两个LLDB指令和Demo地址
setpi
:可以让汇编代码一行一行执行的LLDB指令,可以进入汇编函数内部nexti
:可以让汇编代码一行一行执行的LLDB指令,但是遇到函数直接跳过,不会进入函数内部- 多线程加锁demo地址
2. atomic->原子性
atomic
:用于保证属性setter、getter的原子性操作,相当于在getter和setter内部加了线程同步的锁- 可以参考源码objc4的
objc-accessors.mm
- 它并不能保证使用属性的过程是线程安全的
- 可以参考源码objc4的
- getter方法源码:
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
- setter方法源码:
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{
bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
bool mutableCopy = (shouldCopy == MUTABLE_COPY);
reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
//判断是否是原子性(atomic)
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
3 读写安全
- IOS中如何实现多度单写:
- 同一时间,只能有一个线程进行写的操作
- 同一时间,允许多个线程进项读的操作
- 同一时间,不允许既有写的操作,又有读的操作
- 实现方案:
pthread_rwlock
(读写锁)、dispatch_barrier_async
(异步栅栏调用)
3.1 pthread_rwlock:读写锁
#import <pthread.h>
@interface ViewController ()
@property (nonatomic, assign) pthread_rwlock_t rwlock;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//初始化锁
pthread_rwlock_init(&_rwlock, NULL);
for (int i = 0; i < 10; i++) {
[[[NSThread alloc] initWithTarget:self selector:@selector(read) object:nil] start];
[[[NSThread alloc] initWithTarget:self selector:@selector(write) object:nil] start];
}
}
- (void)read {
//读加锁
pthread_rwlock_rdlock(&_rwlock);
//读 -尝试加锁
//pthread_rwlock_tryrdlock(&_rwlock);
NSLog(@"read===");
pthread_rwlock_unlock(&_rwlock);
}
- (void)write {
//写加锁
pthread_rwlock_wrlock(&_rwlock);
//写-尝试加锁
//pthread_rwlock_trywrlock(&_rwlock);
NSLog(@"write");
pthread_rwlock_unlock(&_rwlock);
}
- (void)dealloc {
//销毁
pthread_rwlock_destroy(&_rwlock);
}
@end
3.2 dispatch_barrier_async
- 这个函数传入的并发队列必须是自己通过dispatch_queue_create创建的
- 如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于
dispatch_async
函数的效果
dispatch_queue_t queue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
//读
dispatch_async(queue, ^{
});
//读
dispatch_async(queue, ^{
});
//写
dispatch_barrier_async(queue, ^{
NSLog(@"截断,等上面任务完之后再无按成下面的任务");
});
//读
dispatch_async(queue, ^{
});
//读
dispatch_async(queue, ^{
});
4 面试题
4.1 说一下OperationQueue和GCD的区别,以及各自的优势
-
我们要明确NSOperationQueue与GCD之间的关系,GCD是面向底层的C语言的API,NSOpertaionQueue用GCD构建封装的,是GCD的高级抽象。
- GCD执行效率更高,而且由于队列中执行的是由block构成的任务,这是一个轻量级的数据结构,写起来更方便
- GCD只支持FIFO的队列,而NSOperationQueue可以通过设置最大并发数,设置优先级,添加依赖关系等调整执行顺序
- NSOperationQueue甚至可以跨队列设置依赖关系,但是GCD只能通过设置串行队列,或者在队列内添加barrier(dispatch_barrier_async)任务,才能控制执行顺序,较为复杂
- NSOperationQueue因为面向对象,所以支持KVO,可以监测operation是否正在执行(isExecuted)、是否结束(isFinished)、是否取消(isCanceld)
-
实际项目开发中,很多时候只是会用到异步操作,不会有特别复杂的线程关系管理,所以苹果推崇的且优化完善、运行快速的GCD是首选
如果考虑异步操作之间的事务性,顺序行,依赖关系,比如多线程并发下载,GCD需要自己写更多的代码来实现,而NSOperationQueue已经内建了这些支持
不论是GCD还是NSOperationQueue,我们接触的都是任务和队列,都没有直接接触到线程,事实上线程管理也的确不需要我们操心,系统对于线程的创建,调度管理和释放都做得很好。而NSThread需要我们自己去管理线程的生命周期,还要考虑线程同步、加锁问题,造成一些性能上的开销