MJiOS底层笔记--多线程

613 阅读5分钟

本文属笔记性质,主要针对自己理解不太透彻的地方进行记录。

推荐系统直接学习小码哥iOS底层原理班---MJ老师的课确实不错,强推一波。


常见的多线程方案


线程的开辟与阻塞机制

  • 并行和串行主要影响:任务的执行方式

并行:多个任务并发(同时)执行 串行:一个任务执行完毕后,再执行下一个任务

  • 同步和异步主要影响:能不能开启新的线程

同步:在当前线程中执行任务,不具备开启新线程的能力 异步:在新的线程中执行任务,具备开启新线程的能力

  • 会开辟新线程的两种情况

    • 并行队列+异步任务 = 多条新线程
    • 自定义串行多列+异步任务 = 一条新线程
  • 其余情况、全部将会置于当前线程/主线程(主队列任务)下执行。

GCD


多线程的安全隐患

资源抢夺

1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源

对共用资源的读写进行枷锁操作


自旋锁

等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源

OSSpinLock


#import <libkern/OSAtomic.h>

@interface OSSpinLockDemo()
@property (assign, nonatomic) OSSpinLock lock;
@end

@implementation OSSpinLockDemo
- (instancetype)init
{
    if (self = [super init]) {
        self.lock = OS_SPINLOCK_INIT;
    }
    return self;
}


- (void)test
{
    OSSpinLockLock(&_lock);
    
    //同步操作
    
    OSSpinLockUnlock(&_lock);
}
@end

优先级反转

OSSpinLock目前已经不再安全,可能会出现优先级反转问题

如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就有可能得不到资源导致无法释放锁。


互斥锁(普通锁)

不可重入,一旦上锁任何访问都会被要求等待。

等待os_unfair_lock锁的线程会处于休眠状态,并非忙等

os_unfair_lock

os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10开始才支持

<os/lock.h>

@interface OSUnfairLockDemo()
@property (assign, nonatomic) os_unfair_lock lock;
@end

@implementation OSUnfairLockDemo

- (instancetype)init
{
    if (self = [super init]) {
        self.lock = OS_UNFAIR_LOCK_INIT;
    }
    return self;
}


- (void)__saleTicket
{
    os_unfair_lock_lock(&_lock);
    
    //耗时操作
    
    os_unfair_lock_unlock(&_lock);
}

@end

pthread_mutex - PTHREAD_MUTEX_DEFAULT&&PTHREAD_MUTEX_NORMAL

PTHREAD_MUTEX_DEFAULT或PTHREAD_MUTEX_NORMAL下的 pthread_mutex 会产生互斥效果

NSLock

NSLock是对mutex普通锁的封装


递归锁

同一线程允许重入,其他线程会被要求等待到该线程完全解锁

递归锁也称为可重入锁。互斥锁可以分为非递归锁/递归锁两种

pthread_mutex - PTHREAD_MUTEX_RECURSIVE

PTHREAD_MUTEX_RECURSIVE 下的 pthread_mutex 会产生递归效果

NSRecursiveLock

NSRecursiveLock也是对mutex递归锁的封装,API跟NSLock基本一致

@synchronized

@synchronized是对mutex递归锁的封装

@synchronized(obj)内部会生成obj对应的递归锁并存在hash表中,然后进行加锁、解锁操作。性能最差

-(void)test {
    @synchronized(self) {
        NSLog(@"2");
        [self tess];
        sleep(5);
    }
}

条件锁

可以让一个线程在加锁途中等待另一个线程完成某个动作后继续加锁执行

类似消费者在商场等着商家调货,然后继续购买。

在上锁状态下可以暂时将锁放开,休眠并等待某个条件

当其他线程对条件发送信号,唤醒继续加锁并执行

#### pthread_mutex - pthread_cond_t

@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) {
        // 等待唤醒
        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_mutex_unlock(&_mutex);
}

- (void)dealloc
{
    pthread_mutex_destroy(&_mutex);
    pthread_cond_destroy(&_cond);
}

@end

NSCondition

NSCondition是对mutex和cond的封装

NSConditionLock

NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值

- (instancetype)init
{
    if (self = [super init]) {
        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 lock];
    
    NSLog(@"__one");
    sleep(1);
    
    [self.conditionLock unlockWithCondition:2];
}

- (void)__two
{
    [self.conditionLock lockWhenCondition:2];
    
    NSLog(@"__two");
    sleep(1);
    
    [self.conditionLock unlockWithCondition:3];
}

dispatch_semaphore

信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步

也可以控制线程的最大并发数

- (void)test
{
    // 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
    // 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    
    sleep(2);
    NSLog(@"test - %@", [NSThread currentThread]);
    
    // 让信号量的值+1
    dispatch_semaphore_signal(self.semaphore);
}

性能排序

  1. os_unfair_lock
  2. OSSpinLock
  3. dispatch_semaphore //推荐
  4. pthread_mutex //推荐
  5. dispatch_queue(DISPATCH_QUEUE_SERIAL)
  6. NSLock
  7. NSCondition
  8. pthread_mutex(recursive)
  9. NSRecursiveLock 10 .NSConditionLock 11 .@synchronized

自旋锁、互斥锁比较

什么情况使用自旋锁比较划算?

  1. 预计线程等待锁的时间很短
  2. 加锁的代码(临界区)经常被调用,但竞争情况很少发生
  3. CPU资源不紧张
  4. 多核处理器

什么情况使用互斥锁比较划算?

  1. 预计线程等待锁的时间较长
  2. 单核处理器
  3. 临界区有IO操作
  4. 临界区代码复杂或者循环量大
  5. 临界区竞争非常激烈

atomic

atomic用于保证属性setter、getter的原子性操作,相当于在getter和setter内部加了线程同步的锁

可以参考源码objc4的objc-accessors


iOS中的读写安全方案

读写锁

dispatch_barrier_async

@interface ViewController ()
@property (strong, nonatomic) dispatch_queue_t queue;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
//    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//    queue.maxConcurrentOperationCount = 5;
    
//    dispatch_semaphore_create(5);
    
    self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 10; i++) {
        dispatch_async(self.queue, ^{
            [self read];
        });
        
        dispatch_async(self.queue, ^{
            [self read];
        });
        
        dispatch_async(self.queue, ^{
            [self read];
        });
        
        dispatch_barrier_async(self.queue, ^{
            [self write];
        });
    }
}


- (void)read {
    sleep(1);
    NSLog(@"read");
}

- (void)write
{
    sleep(1);
    NSLog(@"write");
}

@end

pthread_rwlock_t

@interface ViewController ()
@property (assign, nonatomic) pthread_rwlock_t lock;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 初始化锁
    pthread_rwlock_init(&_lock, NULL);
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            [self read];
        });
        dispatch_async(queue, ^{
            [self write];
        });
    }
}


- (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);
}


@end