【iOS基础】多线程

297 阅读4分钟

【iOS基础】多线程

基础

概念

  1. 进程

    • 在系统中正在运行的一个应用程序
    • 每个进程之间是独立的,运行在其专用的且受保护的内存空间内
    • iOS系统是单进程的
  2. 线程

    • 进程的基本执行单元,
    • 处理器/CPU调度的基本单位
    • 一个进程的所有任务都在线程中执行,进程想要执行任务,必须要有线程(进程至少要有一条线程),程序会默认开启一条线程(主线程/UI线程)
    • 多线程原理:CPU在单位时间片里快速在各个线程之间切换
  3. 时间片: cpu在多个任务之间进行快速切换的时间间隔

进程和线程对比

  对象线程进程
地址空间同一进程下的线程共享本进程的地址空间进程间地址空间独立
资源拥有同一进程下的线程共享本进程的资源(内存、I/O,CPU等)进程间资源独立
崩溃一个线程崩溃→整个进程死掉一个进程崩溃,保护模式下不会对其他进程产生影响
切换1. 涉及频繁切换 2. 同时进行+共享某些变量晋城切换,消耗的资源大,效率低
执行过程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制每个独立进程有一个程序运行的入口和顺序执行序列

意义

优点:

  1. 适当提高程序的执行效率

  2. 适当提高资源利用率(CPU、内存…)

  3. 线程上的任务执行完成后,线程会自动销毁

缺点:

  1. 开启线程耗时(约4ms)

  2. 占用一定的内存空间(iOS中默认每一条线程占512KB,OS X中8MB)

  3. 线程数量多时,CPU在调用线程上的开销大

  4. 线程数量多时,程序设计更复杂,需要考虑线程间通信,多线程数据共享等

iOS中的多线程技术方案

线程调度

线程生命周期

Untitled

线程池调度策略

其中线程的运行与否取决于线程池的调度,调度策略如下图所示:

Untitled

其中饱和策略可能有几种情况

  1. AbortPolicy:直接抛出RejectedExecutionException异常来阻止系统正常运行

  2. DisOldestPolicy:丢掉等待最久的任务

  3. CallerRunsPolicy:将任务回退到调用者

  4. DiscardPolicy:直接丢弃任务

任务执行速率与线程优先级分析

任务是依赖线程执行的,所以在一条线程中,影响任务执行速度的因素如下

  1. CPU的硬件条件+调度情况
  2. 任务的复杂度
  3. 任务的优先级
  4. 线程状态

线程优先级反转

首先我们要知道,线程有如下两种,IO密集型CPU密集型更容易获得优先级提升

  1. IO密集型:频繁等待

  2. CPU密集型:很少等待

注意:优先级提升不代表就会执行

所以线程优先级的影响因素如下:

  1. 用户指定
  2. 等待的频繁度
  3. 长时间不执行 -> 提高优先级

应用

属性的多读单写

  1. 读写锁pthread_rwlock
@interface YKThread () {
    pthread_rwlock_t rwlock;
}
@property (nonatomic, strong) NSMutableArray *list;
@end

@implementation YKThread

  static NSMutableArray * _list;
  
- (instancetype)init {
    if (self = [super init]) {
        self.list = [[NSMutableArray alloc] init];
        pthread_rwlock_init(&rwlock,
                            NULL);
    }
    return self;
}


- (NSMutableArray *)list {
    //加读锁
    pthread_rwlock_rdlock(&rwlock);
    NSMutableArray *result = _list;
    pthread_rwlock_unlock(&rwlock);
    return result;
}

- (void)setList:(NSMutableArray *)list {
    //加写锁
   pthread_rwlock_wrlock(&rwlock);
   _list = [list mutableCopy]; // 注意这里是浅拷贝,如有需要可实现深拷贝相关方法,这里不为重点
   pthread_rwlock_unlock(&rwlock);
}
  1. 栅栏函数dispatch_sync读+dispatch_barrier_sync

    ⚠️仅适用于对外部调用顺序没有要求!

    - dispatch_sync读:并发同步读取数据,能保证外部调用顺序。此时会堵塞当前线程,当前线程需要等待读取任务执行完成,才能继续执行后边代码任务。

    - dispatch_barrier_sync 写:并发异步回调方式读取数据,当你对外部调用顺序没有要求时,可以这么调用。不需要等待写操作完成,所以用异步。

@interface YKThread () {
    dispatch_queue_t concurrentQueue;
}
@property (nonatomic, strong) NSMutableArray *list;
@end

@implementation YKThread

    static NSMutableArray * _list;
    
- (instancetype)init {
    if (self = [super init]) {
        self.list = [[NSMutableArray alloc] init];
        concurrentQueue = dispatch_queue_create("com.yk.barrier.queue",
                       DISPATCH_QUEUE_CONCURRENT);
    }
    return self;
}

- (NSMutableArray *)list {
    __block NSMutableArray *result;
    dispatch_sync(concurrentQueue, ^{
        result = _list;
    });
    return result;
}

- (void)setList:(NSMutableArray *)list {
    dispatch_barrier_async(concurrentQueue,
                           ^{
        _list = list;
    });
}

最大并发数

  1. NSOperationQueue
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
queue.maxConcurrentOperationCount = 3;
  1. 信号量dispatch_semaphore_t
- (void)maxConcurrent {
    int concurrentCount = 2;
    dispatch_semaphore_t sem = dispatch_semaphore_create(concurrentCount);
    for (int i = 0; i < 100; i++) {
        // dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);放在这里
        // 最大并发数为concurrentCount
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
                                     (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
            NSLog(@"Task %d",i);
            dispatch_semaphore_signal(sem);
        });

        // 若dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);放在这里
        // 则最大并发数为(concurrentCount + 1)
    }
}