iOS底层原理10: 多线程

601 阅读3分钟

1. iOS中的多线程技术

  • pthread pthread是一套同样的多线程API, 在Unix/Linux等系统都可以使用
    优点: 跨平台, 可移植
    缺点: 非常底层, 使用难度大(需要扎实的技术)

  • NSThread NSThread是iOS中基于OC提供的多线程API
    优点: 使用面向对象, 简单易用, 可以直接操作线程对象
    缺点: 使用起来没有GCD快速, 需要声明对象, 调用对象方法, 需要自己管理线程的生命周期

  • GCD 基于C语言, 旨在代替NSThread的多线程技术
    优点: 系统自动管理生命周期, 使用简单(Block直接使用), 系统底层会充分利用CPU多核
    缺点: 不能手动管理生命周期

  • NSOperation 基于GCD面向对象的封装
    优点: 基于GCD, 使用面向对象, 可以操作线程的启动取消暂停, 可以添加线程依赖, 获取线程的状态如 finished/cancelled等
    缺点: 使用起来没有GCD快速, 需要声明对象

2. 同步, 异步, 串行, 并行

  • 同步和异步主要是否开启新的线程执行任务
    同步: 在当前线程执行任务, 不开启新线程
    异步: 开启新线程, 在新线程执行任务

  • 串行和并行主要描述执行任务的方式 串行: 多个任务依次执行
    并行: 多个任务并发执行

使用 sync(同步方法)在串行队列添加任务, 会导致死锁

3.多线程同步和锁

多线程的使用是有危险的, 当多个线程同时对一个资源进行读写, 很容易造成数据的错乱. 解决方案就是多线程同步, 是多个线程按照先后顺序同步执行. 常用的手段是加锁

  • OSSpinLock: 自旋锁 自旋锁在等待锁时会处于忙等的状态, 一直占用CPU资源. 可以理解为一个while死循环

  • os_unfair_lock os_unfair_lock用于取代不安全的OSSpinLock, 在底层等待锁时, CPU会进入休眠, 而不是忙等

  • pthread_mutex: 互斥锁 互斥锁的等待锁会处于休眠状态, 使用 PTHREAD_MUTEX_RECURSIVE type初始化可以解决递归锁的问题

  • NSLock: 对pthread_mutex的封装

  • NSRecursiveLock: 对pthread_mutex递归锁的封装

  • dispatch_semaphore: 信号量 当信号量的值<=0时, 当前线程就会进入休眠等待, 当信号量 >0 时会执行wait()后的代码; 通过初始化信号量来控制线程并发的最大数量. dispatch_semaphore_wait(): 使信号量 -1 dispatch_semaphore_signal(): 使信号量 +1

  • dispatch_queue: GCD dispatch_queue在初始化时, 可以选择使用串行队列, 那么在队列中的任务就会同步执行

  • @synchronized 对pthread_mutex递归锁的封装

  • 自旋锁和互斥锁 自旋锁: 等待锁时会一直占用CPU资源, 处于忙等. 因为会一直占用CPU资源, 所以适合等待锁时间短, CPU资源不紧张时使用.
    互斥锁: 等待锁时会进入休眠, 一直等待唤醒. 因为休眠唤醒需要更多的资源, 所以适合用在等待锁时间比较长的场景.

  • dispatch_barrier_async/dispatch_barrier_sync: 线程栅栏 使用线程栅栏可以将栅栏前后的任务进行分割, 保证栅栏前边的任务执行完成后再执行栅栏后的任务

4.多线程的使用场景

  • 实现多线程卖票: 多个线程同时卖票, 要保证票的数量正确
// 设定初始值为10, 开启3个线程进行卖票
- (void)ticketTest {
    self.count = 10;
    self.ticketLock = [[NSLock alloc] init];
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self saleTicket];
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            [self saleTicket];
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            [self saleTicket];
        }
    });
}

// 在开始时对线程进行加锁, 处理完成以后进行解锁保证线程同步
- (void)saleTicket {
    [self.ticketLock lock];
    
    int tempCount = self.count;
    sleep(.3);
    tempCount--;
    self.count = tempCount;
    
    [self.ticketLock unlock];
    NSLog(@"还剩%d张票 ", self.count);
}

  • 设定有A, B, C, D 4个任务, 要求C, D任务要在A, B完成以后在进行
  1. GCD的group
// 定义group 和 queue
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

dispatch_group_async(group, queue, ^{
    NSLog(@"A");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"B");
    sleep(1);  // 假设B任务延迟执行完成
});

// 在group完成的notify中执行任务C, D
dispatch_group_notify(group, queue, ^{
    NSLog(@"C, D");
});
  1. NSOperation设置依赖
// 创建queue
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

NSBlockOperation *opA = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"A");
}];
NSBlockOperation *opB = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"B");
    sleep(1);  // 假设B任务延迟执行完成
}];
NSBlockOperation *opCD = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"C, D");
}];

// 设置B依赖于A, CD依赖于B
[opB addDependency:opA];
[opCD addDependency:opB];

// 将任务加入队列执行
[queue addOperation:opA];
[queue addOperation:opB];
[queue addOperation:opCD];

使用NSOperation则必须同时设置多个依赖才可以完成

  1. dispatch_barrier_async线程栅栏
// 创建队列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    
dispatch_async(queue, ^{
    NSLog(@"A");
});

dispatch_async(queue, ^{
    NSLog(@"B");
    sleep(1); // 假设B任务延迟执行完成
});

// 设置线程栅栏
dispatch_barrier_async(queue, ^{
    NSLog(@"barrier");
});

dispatch_async(queue, ^{
    NSLog(@"C");
});

dispatch_async(queue, ^{
    NSLog(@"D");
});