OC底层学习-多线程

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)
      • 让任务一个接一个的执行(一个任务执行完毕之后,在执行下一个任务)
  • 比较容易混淆的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: 表示一直等,等到信号量的值> 0
    • DISPATCH_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_semaphorepthread_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
    • 它并不能保证使用属性的过程是线程安全的
  • 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的高级抽象。

    1. GCD执行效率更高,而且由于队列中执行的是由block构成的任务,这是一个轻量级的数据结构,写起来更方便
    2. GCD只支持FIFO的队列,而NSOperationQueue可以通过设置最大并发数,设置优先级,添加依赖关系等调整执行顺序
    3. NSOperationQueue甚至可以跨队列设置依赖关系,但是GCD只能通过设置串行队列,或者在队列内添加barrier(dispatch_barrier_async)任务,才能控制执行顺序,较为复杂
    4. NSOperationQueue因为面向对象,所以支持KVO,可以监测operation是否正在执行(isExecuted)、是否结束(isFinished)、是否取消(isCanceld)
  • 实际项目开发中,很多时候只是会用到异步操作,不会有特别复杂的线程关系管理,所以苹果推崇的且优化完善、运行快速的GCD是首选
    如果考虑异步操作之间的事务性,顺序行,依赖关系,比如多线程并发下载,GCD需要自己写更多的代码来实现,而NSOperationQueue已经内建了这些支持
    不论是GCD还是NSOperationQueue,我们接触的都是任务和队列,都没有直接接触到线程,事实上线程管理也的确不需要我们操心,系统对于线程的创建,调度管理和释放都做得很好。而NSThread需要我们自己去管理线程的生命周期,还要考虑线程同步、加锁问题,造成一些性能上的开销

  • IOS之多线程

  • swift之GCD

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值