Objective-C 多线程编程与GCD | 青训营笔记

181 阅读8分钟

OC Block

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

背景知识

进程与线程

  • 进程 Process
    • 程序是运行的一个实例,是资源分配的最小单位。
    • 独立地运行在其专用受保护的内存空间内
    • 一个进程可包含多个线程
  • 线程 Thread
    • 是操作系统实施调度的最小单位。
    • 同一进程的线程共享线程的内存空间(代码段,数据段,堆,文件资源等)。
    • 每个线程有独立的寄存器,栈,线程局部存储等私有数据。

串行, 并行, 并发

  • 串行 serial computing: 多个任务按顺序执行
  • 并行 parallel computing: 多个任务同意时刻被执行
  • 并发 concurrent computing: 多个任务在同一时间段内被执行, 侧重现象的"发生"

线程的生命周期与线程调度

  • 线程的生命周期: 新建(new),就绪(runnable),运行(running),阻塞(block),死亡(dead)
  • 线程调度: 多个线程根据系统调度规则轮流被处理器执行一小段时间(不一定完成执行)。

多线程编程的优缺点

  • 优点: 提高程序的执行效率和资源利用率
  • 缺点: 线程维护(创建与调度)系统开销可能很大, 错误的调度可能会引起线程安全问题, 也会增加程序设计的复杂度。

IOS的多线程

Runloop

  • 事件接收和分发机制的一个实现
  • 用于线程的持续运行与维护线程中的各种事件
  • 程序主线程默认开启Runloop, 其他线程需要手动开启

主线程 Main Thread

  • IOS App 启动时默认开启的一条线程, 默认开启Runloop
  • 负责UI界面的刷新与UI交互事件的处理, 因此主线程也被称为"UI线程"
  • 在多线程编程的理念中, 耗时操作被分配到子线程来执行

多线程编程实现方案对比

  • Pthread
    • POSIX Threads, portable Portable Operating System Interface Thread.
    • 一套基于C语言的通用的多线程API, 适用于类Unix\Windows等系统, 跨平台, 可移植
    • 程序员管理线程生命周期
  • NSThread
    • 一套苹果官方提供的基于OC语言的面向对象多线程API, 可通过KVO监听部分属性
    • 程序员管理线程生命周期
  • GCD
    • Grand Central Dispatch
    • 一套苹果官方提供的基于C语言的异步执行任务的技术之一, 系统级的线程管理
    • 抽象出了任务、队列等概念, 开发者只需集中于任务的编写和派发
    • 系统自动使用多核, 自动管理线程生命周期
  • NSOperation, NSoperationQueue
    • 一套苹果官方提供的基于GCD (OC语言) 的面向对象多线程API, 可通过KVO监听任务状态
    • 可指定任务之间的依赖关系, 控制执行顺序, 设定任务并发数

GCD 中央调度

任务与队列

  • 任务 Task: 线程中执行的代码, 以block的形式提交到队列
  • 队列 Queue: 任务派发队列, 队列中的任务按先进先出(FIFO)的次序被派发到线程进行处理

串行队列与并发队列

  • 串行队列 Serial Dispatch Queue: 队列中的任务在单线程中依序执行, 当前执行的任务结束后才能执行下一个任务
  • 并行队列 Concurrent Dispatch Queue: 在异步执行的情况下, 队列中的任务可被分发到多个线程下同同时执行

IOS 队列获取与创建

//获取系统队列
//Main dispatch queue - 主队列, 串行队列, 只在主线程中执行
dispatch_queue_t mainQueue = dispatch_get_main_queue();

//Global Dispatch Queue - 全局队列, 并发队列, 有4种优先级
//DISPATCH_QUEUE_HIGH, DISPATCH_QUEUE_DEFAULT, DISPATCH_QUEUE_LOW, DISPATCH_QUEUE_BACKGROUND
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_HIGH, 0);

//创建自定义队列
//DISPATCH_QUEUE_SERIAL - 串行队列; DISPATCH_QUEUE_CONCURRENT - 并行队列
dispatch_queue_t myQueue = dispatch_queue_create("myQueueLabel", DISPATCH_QUEUE_SERIAL);

同步执行与异步执行

//同步执行
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
//异步执行
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
是否等待任务执行结束(阻塞队列后续任务的执行)并行队列 (Concurrent Dispatch Queue)串行队列 (Concurrent Dispatch Queue), 非主队列主队列
同步执行(dispatch_sync)不开启新线程, 在当前线程执行任务不开启新线程, 在当前线程执行任务发生死锁
异步执行(dispatch_async)开启(多个)新线程, 并行执行队列中的任务开启(单个)新线程, 串行执行队列中的任务不开启新线程, 在主线程下一个Runloop执行任务

GCD常用API

dispatch_after

  • 延时提交任务到队列
//延时时间为三秒
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PERSEC); 

//void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
dispatch_after(time, myQueue, ^{
    //do something
});

dispatch_group, dispatch_group_notify 和 dispatch_group_wait

  • 希望一个Dispatch Queue 多个任务执行完后再执行某个任务
  • dispatch_group_notify是一个异步API,因此不会阻塞当前线程的执行
  • 可以用dispatch_group_wait设置等待上限 (超时等待),是一个同步API,因此会阻塞当前线程的执行(等待当前任务执行结束或者超时)
//创建 dispatch group
dispatch_group_t group = dispatch_group_create();

//追加任务到myQueue和group里
dispatch_group_async(group, myQueue, block0);
dispatch_group_async(group, myQueue, block1);

//group 中所有任务完成后告知主线程执行指定任务
//void dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    //do something
});

//超时等待 1: 永久等待group执行完,效果同 dispatch_group_notify
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

//超时等待 2: 等待group至多执行5秒
dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PERSEC); 
//通过返回值来查看任务执行情况
long result = dispatch_group_wait(group, time); 
//result = 0: group中所有任务都已执行结束
//result != 0: 超时

dispatch_apply

  • 快速迭代, 按照指定次数讲指定任务追加到派发队列,并等待队列中所有任务执行结束, 多用于并发队列。
  • dispatch_apply同步API。
  • 由于dispatch_apply 会等待队列任务结束,如果在主线程中使用会引起卡顿或者死锁, 尽量在 dispatch_async中异步执行。

dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);


dispatch_async(globalQueue, ^{
    //子线程异步执行
    //void dispatch_apply(size_t iterations, dispatch_queue_t queue, void(^block)(size_t iteration));
    dispatch_apply(10, globalQueue, ^(size_t iteration){
    NSlog(@"这是第 %@ 次循环", @(iteration));
});
    //回到主线程执行
    dispatch_async(mainQueue, ^{
        NSLog(@"done");
    });
});


dispatch_once

  • 任务在整个程序生命周期中只执行一次, 常用于单例模式
//以下下代码保证单例 Singleton 在整个程序生命周期只会初始化一个.
static Singleton *instance = nil;
+ (Singleton *)sharedInstance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [Instance new];
    });
    return instance;
}

线程安全和线程同步

  • 原子操作 Atomic Instruction: 会被编译成只有单个机器指令执行的操作。
  • 临界区 Critical Session: 不能被并发执行的一段代码, 如共享数据、代码块。
  • 线程同步: 每个线程对临界区的访问都是原子操作, 即多个线程不能同时访问临界区。

锁:

互斥锁,递归锁,条件变量,信号量...

互斥锁 Mutex Lock

  • 临界区资源都会有互斥锁。
  • 线程在访问临界区资源时会先请求获取对应的锁。如果锁未被占用,即可获取锁以及访问资源,访问结束后再释放锁; 如果锁被其他线程占用,便会等待直到锁重新可用。

信号量 Semaphore

  • 允许多个线程并发访问资源,实现线程同步或者控制并发访问数量
  • 临界区资源都会有个信号量,它代表当前还可以允许多少个线程申请并发访问。
  • 线程访问资源前会先获取信号量
    • 信号量-=1
    • 如果信号量小于0,线程进入等待状态,否则继续执行。
  • 线程访问结束后释放信号量
    • 信号量+=1
    • 如果信号量大于0,唤醒等待的线程来获取资源并执行。

死锁 Deathlock

产生死锁的四个必要条件:

  • 互斥 Mututual Exclusion: 资源在同一时间只能被一个线程所持有
  • 占有并等待 Hold and block: 线程可以在持有了某个资源的同时去请求其他资源
  • 不可抢占 No Pre-emption: 线程不可以夺取被其他线程所占有的资源,只能等待占有资源的线程主动释放
  • 循环等待 Circular Waiting: 线程等待形成环路
    • 如:线程1占有A等待B, 线程2占有B等待A。

打破死锁的任意一个条件即可预防死锁的产生

OC 的线程安全与同步

@property 的原子属性 (atomic, nonatomic)

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

dispatch_barrier_async

  • 多线程场景下,不允许多写操作并发执行,不允许读写操作并发执行,但允许多读操作并发执行
  • dispatch_barrier_async可用于等待当前队列所有任务执行结束后,再追加指定任务X到队列,后续提交到任务也需要等待任务X执行结束.
  • dispatch_barrier_async同步API
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//并发读操作
dispatch_async(globalQueue, block0_for_reading);
dispatch_async(globalQueue, block1_for_reading);

//串行写操作
//dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
dispatch_barrier_async(globalQueue, block0_for_writing);
dispatch_barrier_async(globalQueue, block0_for_writing);

//并发读操作-需等待前面写操作执行结束
dispatch_async(globalQueue, block2_for_reading);
dispatch_async(globalQueue, block3_for_reading);

Dispatch Semaphore

  • GCD 提供的信号量接口