iOS开发基础36-多线程之GCD

GCD 深度解析:队列与任务、同步异步、栅栏函数、队列组与单例模式

本文系统梳理 GCD(Grand Central Dispatch)并发编程框架:任务与队列核心概念、同步/异步执行、栅栏函数、并发/串行队列创建、四种组合效果、线程间通信、延时/一次性/快速迭代/队列组/挂起恢复/GCD 定时器等高级用法,以及单例模式实现。


一、GCD 基本概念

1. 简介

GCD(Grand Central Dispatch)是一套基于 C 语言的底层并发 API,用于任务的分发与执行。

优势:

  • 多核优化:自动利用设备多核 CPU,线程数由系统根据核心数和负载动态管理。
  • 自动线程管理:自动创建、调度、销毁线程,开发者无需手动管理线程生命周期。
  • 简化代码:只需定义任务(block)并添加到队列,无需编写复杂线程管理代码。

2. 任务与队列

GCD 两个核心概念:

  • 任务(block):需要执行的具体操作。
  • 队列(dispatch queue):存放任务的容器,GCD 按 FIFO(先进先出)原则取出任务并放到对应线程执行。

FIFO 指任务取出的顺序。并发队列中任务取出后并发执行,执行完成顺序不一定是 FIFO;串行队列中任务取出后顺序执行,完成顺序即 FIFO。

使用两步:

  1. 定制任务(确定执行的操作);
  2. 将任务添加到队列(GCD 自动调度执行)。

二、任务执行方式

1. 同步与异步

执行方式 函数 是否开启新线程 是否阻塞当前线程
同步(sync) dispatch_sync 否(在当前线程执行) 是(等待任务完成)
异步(async) dispatch_async 是(具备开启新线程能力) 否(不等待,立即返回)
// 同步执行(当前线程等待任务完成)
dispatch_sync(queue, ^{
    NSLog(@"同步任务");
});

// 异步执行(不等待,可能开启新线程)
dispatch_async(queue, ^{
    NSLog(@"异步任务");
});

异步在串行队列中也可能不开启新线程(GCD 内部优化),但具备开启能力;在并发队列中通常开启新线程并发执行。

2. 栅栏函数(dispatch_barrier)

栅栏函数在自定义并发队列中,保证前面的任务全部执行完后才执行栅栏任务,栅栏任务完成后才执行后面的任务,适用于"多读单写"场景:

dispatch_queue_t queue = dispatch_queue_create("com.example.rw", DISPATCH_QUEUE_CONCURRENT);

// 读操作(并发)
dispatch_async(queue, ^{ NSLog(@"读1"); });
dispatch_async(queue, ^{ NSLog(@"读2"); });

// 写操作(栅栏:前面的读全部完成后才执行写,写完成后才执行后面的读)
dispatch_barrier_async(queue, ^{
    NSLog(@"写操作(独占)");
});

// 后续读操作
dispatch_async(queue, ^{ NSLog(@"读3"); });

栅栏函数注意事项:

  • 在自定义并发队列中有效;
  • 在全局并发队列中,dispatch_barrier_async 行为等同于 dispatch_async(不栅栏),因为全局队列被系统共享,不能被栅栏阻塞;
  • 在串行队列中无意义(串行队列本来就是顺序执行);
  • 同步栅栏 dispatch_barrier_sync 会阻塞当前线程。

三、队列类型与创建

1. 队列类型

队列类型 执行方式 说明
并发队列(Concurrent) 多任务同时执行 任务取出后并发执行,自动开启多条线程
串行队列(Serial) 任务顺序执行 一个任务完成后才执行下一个,只开启一条线程

2. 创建队列

// 自定义并发队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.concurrent", DISPATCH_QUEUE_CONCURRENT);

// 自定义串行队列(NULL 等同于 DISPATCH_QUEUE_SERIAL)
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.serial", DISPATCH_QUEUE_SERIAL);

队列标签(label)用于调试,建议使用反向域名格式,不影响功能。

3. 全局并发队列

系统提供的全局并发队列,按服务质量(QoS)分优先级:

// 获取全局并发队列(iOS 8+ 推荐 QoS 方式)
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
QoS 优先级 说明
QOS_CLASS_USER_INTERACTIVE 用户交互(最高,动画、事件响应)
QOS_CLASS_USER_INITIATED 用户发起(用户明确请求,需立即结果)
QOS_CLASS_DEFAULT 默认
QOS_CLASS_UTILITY 工具类(耗时操作,进度可见)
QOS_CLASS_BACKGROUND 后台(最低,用户不可见,如备份、同步)

iOS 8 之前使用 DISPATCH_QUEUE_PRIORITY_HIGH/DEFAULT/LOW/BACKGROUND,iOS 8+ 映射到对应 QoS,仍可使用但推荐 QoS 常量。

4. 主队列

GCD 自带的特殊串行队列,任务在主线程执行,用于更新 UI:

dispatch_queue_t mainQueue = dispatch_get_main_queue();

四、同步/异步与并发/串行的组合

组合 是否开启新线程 执行方式
同步 + 并发队列 否(当前线程) 当前线程顺序执行任务(不并发)
同步 + 串行队列 否(当前线程) 当前线程顺序执行任务
异步 + 并发队列 是(多条新线程) 多任务并发执行
异步 + 串行队列 是(一条新线程) 任务在新线程顺序执行

主队列死锁:在主线程中调用 dispatch_sync(dispatch_get_main_queue(), ^{...}) 会导致死锁——主线程等待 sync 任务完成,而 sync 任务在主队列中等待主线程执行,互相等待。同步执行主队列任务必须在非主线程中调用。


五、线程间通信

耗时操作在子线程执行,完成后回到主线程更新 UI:

dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
    // 子线程:耗时操作(网络请求、文件处理、图片解码)
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:data];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        // 主线程:更新 UI
        self.imageView.image = image;
    });
});

六、GCD 高级用法

1. 延时执行

// 2 秒后在主队列执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)),
               dispatch_get_main_queue(), ^{
    NSLog(@"2 秒后执行");
});

dispatch_after 不是精确的定时器,是在指定时间后将任务添加到队列,任务执行时间取决于队列状态。

2. 一次性执行(dispatch_once)

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 整个程序生命周期内只执行一次,线程安全
    NSLog(@"只执行一次");
});

dispatch_once 线程安全,是实现单例的标准方式。Swift 中 static let 常量天然线程安全,无需 dispatch_once。

3. 快速迭代(dispatch_apply)

// 并发执行 10 次迭代(index 从 0 到 9)
// dispatch_apply 是同步的,会阻塞当前线程直到所有迭代完成
dispatch_apply(10, dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^(size_t index) {
    NSLog(@"迭代 %zu", index);
});
NSLog(@"所有迭代完成");

dispatch_apply 在并发队列中迭代并发执行,但函数本身同步阻塞当前线程,直到所有迭代完成。适合在后台线程中处理可并行的循环任务。

4. 队列组(dispatch_group)

基本用法:group_async + notify

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);

dispatch_group_async(group, queue, ^{
    NSLog(@"任务1");
});
dispatch_group_async(group, queue, ^{
    NSLog(@"任务2");
});

// 所有任务完成后,在主队列执行
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"所有任务完成");
});

// 或阻塞等待所有任务完成(慎用,会阻塞当前线程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

手动管理:enter/leave(用于 block 内的异步操作)

当任务 block 内部还有异步操作(如网络请求),dispatch_group_async 无法感知内部异步完成,需用 enter/leave 手动管理:

dispatch_group_t group = dispatch_group_create();

dispatch_group_enter(group); // 进入组(任务开始)
[self loadDataWithCompletion:^{
    NSLog(@"数据加载完成");
    dispatch_group_leave(group); // 离开组(任务完成)
}];

dispatch_group_enter(group);
[self loadImageWithCompletion:^{
    NSLog(@"图片加载完成");
    dispatch_group_leave(group);
}];

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"数据和图片都加载完成");
});

enter 和 leave 必须成对出现,leave 次数不能超过 enter,否则崩溃。

5. 队列挂起与恢复

// 挂起队列(暂停执行新任务,正在执行的任务不受影响)
dispatch_suspend(queue);

// 恢复队列
dispatch_resume(queue);

dispatch_suspend 是异步的,只影响后续取出的任务,不影响正在执行的任务。挂起和恢复必须成对调用。

6. GCD 定时器(dispatch_source_t)

GCD 定时器比 NSTimer 更精准,不依赖 RunLoop,在子线程也可执行:

@property (nonatomic, strong) dispatch_source_t timer;

- (void)startTimer {
    // 创建定时器源(DISPATCH_SOURCE_TYPE_TIMER)
    self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
    
    // 设置定时器:起始时间、间隔(秒)、误差(0 为最精准)
    dispatch_source_set_timer(self.timer,
                              dispatch_time(DISPATCH_TIME_NOW, 0),
                              1.0 * NSEC_PER_SEC,
                              0);
    
    // 设置回调
    dispatch_source_set_event_handler(self.timer, ^{
        NSLog(@"定时器触发");
    });
    
    // 启动定时器(GCD 源默认是挂起的,必须 resume 才开始)
    dispatch_resume(self.timer);
}

- (void)stopTimer {
    if (self.timer) {
        dispatch_cancel(self.timer);
        self.timer = nil;
    }
}

GCD 定时器创建后默认挂起,必须调用 dispatch_resume 才开始执行。取消后不可恢复,需重新创建。


七、单例模式

1. 作用

程序运行期间保证某个类只有一个实例,提供全局访问点,常用于共享资源(网络管理器、数据管理器)。

2. ARC 单例实现

@interface DataManager : NSObject <NSCopying>
+ (instancetype)sharedManager;
@end

@implementation DataManager

static id _instance;

+ (instancetype)sharedManager {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[self alloc] init];
    });
    return _instance;
}

// 重写 allocWithZone,防止通过 alloc 创建新实例
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [super allocWithZone:zone];
    });
    return _instance;
}

// 遵循 NSCopying 时重写 copyWithZone,返回同一实例
- (id)copyWithZone:(NSZone *)zone {
    return _instance;
}

@end

如果类不遵循 NSCopying,则无需实现 copyWithZone。如需支持 mutableCopy,需遵循 NSMutableCopying 并实现 mutableCopyWithZone:。


八、Swift 版本对照

队列与任务

// 主队列
let main = DispatchQueue.main

// 全局队列
let global = DispatchQueue.global()
let userInteractive = DispatchQueue.global(qos: .userInteractive)

// 自定义串行队列
let serial = DispatchQueue(label: "com.example.serial")

// 自定义并发队列
let concurrent = DispatchQueue(label: "com.example.concurrent", attributes: .concurrent)

// 异步执行
global.async {
    print("耗时操作")
    DispatchQueue.main.async {
        print("回主线程更新 UI")
    }
}

// 同步执行(注意:主队列同步会死锁)
serial.sync {
    print("同步任务")
}

栅栏函数

let queue = DispatchQueue(label: "com.example.rw", attributes: .concurrent)
queue.async { print("读1") }
queue.async { print("读2") }
queue.async(flags: .barrier) {
    print("写操作(独占)")
}
queue.async { print("读3") }

延时执行

DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
    print("2 秒后执行")
}

一次性代码(Swift 中 static let 天然线程安全)

final class DataManager {
    static let shared = DataManager()
    private init() {}
}

快速迭代

DispatchQueue.concurrentPerform(iterations: 10) { index in
    print("迭代 \(index)")
}

队列组

let group = DispatchGroup()
let queue = DispatchQueue.global()

queue.async(group: group) { print("任务1") }
queue.async(group: group) { print("任务2") }

group.notify(queue: .main) {
    print("所有任务完成")
}

// 手动 enter/leave
group.enter()
loadData {
    print("数据完成")
    group.leave()
}
group.notify(queue: .main) { print("完成") }

GCD 定时器

var timer: DispatchSourceTimer?

func startTimer() {
    timer = DispatchSource.makeTimerSource(queue: .main)
    timer?.schedule(deadline: .now(), repeating: 1.0)
    timer?.setEventHandler {
        print("定时器触发")
    }
    timer?.resume() // 必须 resume 才开始
}

func stopTimer() {
    timer?.cancel()
    timer = nil
}

单例

final class DataManager {
    static let shared = DataManager()
    private init() {} // 禁止外部创建
}

九、总结

  • 核心概念:任务(block)+ 队列(dispatch queue),GCD 按 FIFO 取出任务并调度执行,自动管理线程生命周期。
  • 同步/异步:同步在当前线程执行并阻塞等待;异步不等待,具备开启新线程能力。
  • 并发/串行:并发队列多任务同时执行(自动开多线程);串行队列任务顺序执行(单线程)。
  • 栅栏函数:dispatch_barrier_async 在自定义并发队列中实现多读单写;全局队列中无效(等同普通 async),串行队列中无意义。
  • 队列类型:主队列(串行,主线程)、全局并发队列(QoS 分 5 档)、自定义串行/并发队列。
  • 主队列死锁:主线程中 dispatch_sync 主队列任务会死锁。
  • 线程通信:全局队列异步执行耗时操作 → 主队列异步更新 UI。
  • 高级用法:dispatch_after 延时、dispatch_once 一次性(单例)、dispatch_apply 快速迭代(同步阻塞)、dispatch_group 队列组(async/notify/enter/leave/wait)、dispatch_suspend/resume 挂起恢复、dispatch_source_t GCD 定时器(比 NSTimer 精准)。
  • 单例:ARC 中 dispatch_once + 重写 allocWithZone + copyWithZone(遵循 NSCopying 时);Swift 中 static let shared 天然线程安全。

posted @ 2015-08-17 21:04  Mr.陳  阅读(387)  评论(0)    收藏  举报