iOS多线程和GCD | 青训营笔记

926 阅读6分钟

这是我参与「第四届青训营 -iOS场」笔记创作活动的的第3篇笔记

多线程

常识

进程与线程

  • 进程是资源分配的最小单位,独立运行在专用并受保护的内存空间中,可以包含多个线程
  • 线程是操作系统调用的最小单位,私有寄存器、栈、线程局部存储(TLS),共享进程的内存空间(代码段、数据段、堆、文件资源等)

串行、并行、并发

  • 串行,一个个执行
  • 并行,多个任务在同一时刻被执行
  • 并发,同一时间段,多个任务切换执行
  • 多线程编程,让多个CPU并发处理多个线程的指令

线程生命周期

  • 新建、就绪、运行、阻塞、死亡
  • 单个CPU上运行多个线程,会让每个线程轮流执行一小段时间片,进行不断切换执行

iOS中多线程

  • POSIX Thread,类Unix系统通用的多线程API,跨平台/可移植,C语言接口
  • NSThread,OC接口,通过KVO监听属性,需要手动管理生命周期
  • gcd,Grand Central Dispatch,开发者只需要注重任务编写,自动使用更多CPU内核,管理线程生命周期
  • NSOperation,OC接口,面向对象,支持设定任务并发数,用于KVO监听任务状态,设定任务的依赖关系

runloop

  • 事件接受和分发机制的实现,让线程在适当的时间处理任务不会退出
  • runloop,在程序运行时就会启动
  • 主线程又称UI线程,主要用于描绘UI和UI交互,耗时操作应当放在子线程中

gcd

  • 任务,用block形式提交任务
  • 队列,任务派发队列,先进先出,追加的形式加入到队列
  1. 串行队列,任务顺序执行,任务结束后,才能执行下一个任务,单个线程依次执行
  2. 并发队列,异步执行,分发到多个线程执行

队列获取

  • Dispatch Queue,提供主队列(串行队列,任务都会被派发到主线程)、global(并发队列,定义四种不同优先级的队列)
  • 自定义队列
// DISPATCH_QUEUE_SERIAL 串行队列
// DISPATCH_QUEUE_CONCURRENT 并行队列
dispatch_queue_create("queue.name", DISPATCH_QUEUE_CONCURRENT);

执行

  • 同步执行,等待执行完,才能继续执行,不具备开启线程的能力
  1. 并发队列,不开启新线程,在当前线程串行执行
  2. 串行队列(非主队列),不开启新线程,在当前线程串行执行
  3. 主队列(主队列任务只会交给主线程执行),发生死锁,dispatch_sync等待主队列中任务被主线程执行,主线程等待dispatch_sync被执行完成
dispatch_sync(<#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)
  • 异步执行,提交到执行队列,不等待执行队列,可以在新的线程执行任务,具备开启线程的能力
  1. 并发队列,开启新线程,在新线程并发执行
  2. 串行队列(非主队列),开启新线程,在新线程串行执行
  3. 主线程,不开启新线程,在当前线程的下一个runloop执行任务
dispatch_async(<#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)

多线程辅助工具

延时执行

  • dispatch_after,3秒后往主队列追加到派发队列,执行时间不确定
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_MSEC);
// 延时时间,执行的队列,执行block
dispatch_after(time, dispatch_get_main_queue(), ^{
    //
});

组执行

  • 相当于CountDownLatch,全部执行完成后再执行notify的动作,异步执行
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, block0);
dispatch_group_async(group, queue, block1);
dispatch_group_async(group, queue, block2);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// block...
dispatch_group_notify(group, mainQueue, ^{
    // 任务全部执行完成后,执行当前notify中的任务,追加到主队列中
});
  • 超时等待,阻塞当前线程等待
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, block0);
dispatch_group_async(group, queue, block1);
dispatch_group_async(group, queue, block2);
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_MSEC);
// 最多等待group任务执行多久time,根据返回值不同判断是执行完成返回,还是超时返回
// 0 执行完成,非0超时
long result = dispatch_group_wait(group, time);
// 其他处理...

快速迭代

  • dispatch_apply,按照执行的次数将指定任务追加到队列中,如果传入串行队列,会顺序遍历,性能不如普通遍历
  • 同步执行接口,会阻塞当前线程,直到任务执行完才会继续执行
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(3, queue, ^(size_t iteration) {
    NSLog(@"%ld", iteration);
});
  • 避免直接在主线程执行,阻塞主线程运行
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
    // 子线程遍历
    dispatch_apply(3, globalQueue, ^(size_t iteration) {
        //..
    });

    dispatch_async(mainQueue, ^{
        //.. 回到主线程执行
    });
});

once

  • 某些操作在整个声明周期只执行一次
  • 单例模式
+ (instancetype)sharedInstance {
    // 只会被初始化一次
    static CommonSortUtil *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (!instance) {
            instance = [[CommonSortUtil alloc] init];
        }
    });
    return instance;
}

线程安全

  • 原子操作,单个机器指令执行的操作,高级语言写出一条语句,往往是多条机器指令
  • 临界区,不能被并发执行的一段代码(共享数据、代码块)
  • 线程同步,一个线程访问临界区的时候,其他线程不能对临界区进行访问,对临界区的访问变成原子性的
  • 锁,互斥量是最简单的锁,锁被一个线程占用时,其他线程尝试获取时只能进行等待,直到被释放;获取锁的线程可以进行重用,还有递归锁、读写锁、条件变量、信号量

死锁,多个线程执行过程中,由于竞争资源导致互相阻塞等待;不可抢占有、相互等待、等待且占有、循环等待 image.png

atomic和nonatomic

  • @property的属性
  • atomic,默认,对属性getter/setter调用是线程安全的,需要耗费资源为属性加锁
  • nonatomic,访问不是线程安全的,访问效率比atomic高

栅栏

  • 实现读写锁,读读共享,读写互斥
  • dispatch_barrier_async等待前面的任务执行完毕,才会继续执行后面的任务
// 全局队列实现不了
// dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t globalQueue = dispatch_queue_create("queue.init", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(globalQueue, ^{
    NSLog(@"read1...");
});
dispatch_async(globalQueue, ^{
    NSLog(@"read2...");
});
dispatch_async(globalQueue, ^{
    NSLog(@"read3...");
});
// 写操作
dispatch_barrier_async(globalQueue, ^{
    for (int i = 0; i <= 50000000; i++) {}
    NSLog(@"write1...");
});
dispatch_async(globalQueue, ^{
    NSLog(@"read4...");
});
dispatch_async(globalQueue, ^{
    NSLog(@"read5...");
});
dispatch_barrier_async(globalQueue, ^{
    NSLog(@"write2...");
});
dispatch_async(globalQueue, ^{
    NSLog(@"read6...");
});
NSLog(@"end...");
[NSThread sleepForTimeInterval:100000];

信号量

  • 信号量大于0,可以减一;信号量等于0,阻塞等待

初始值为1,可以实现互斥锁的效果

  • 创建
dispatch_semaphore_create(3)
  • 等待,永久等待semaphore,为0时阻塞等待
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
  • 交还信号量
dispatch_semaphore_signal(semaphore)

应用场景

  • 从网络加载图片
  • dispatch_group_t实现全部请求返回后,再进行刷新任务
  • 线程安全容器类
  1. 读写锁
  2. 移除时,用栅栏做隔离
- (id)getObject {
    id lastObject = nil;
    // 阻塞获取元素,任务派发到concurrentQueue,但是串行执行
    dispatch_sync(self.concurrentQueue, ^{
        lastObject = [self.array lastObject];
    });
    return lastObject;
}
- (void)removeLastObject {
    // 等待队列中前面的任务被执行完毕,再进行执行
    dispatch_barrier_async(self.concurrentQueue, ^{
        [self.array removeLastObject];
    });
}

常见问题

  • 死锁
  1. 递归获取非递归锁
  2. 两个线程互相等待
  • 非主线程操作UI
  • 线程不安全的容器读写崩溃,数组越界/野指针访问