IOS开发,对GCD深入了解!

进一步理解GCD(Grand Central Dispatch),我们需要从其核心概念、核心组件、关键特性入手,再结合实际开发场景分析案例。GCD是苹果提供的基于C语言的多线程技术,其核心是通过「队列」管理「任务」,自动调度线程执行,无需手动管理线程生命周期,高效且易用。


一、GCD核心概念与组件


1. 核心目标


GCD的本质是任务调度系统:将任务(代码块)提交到队列,由系统自动分配线程执行任务,开发者只需关注「任务是什么」和「用什么队列执行」,无需关心线程的创建/销毁。


2. 核心组件:Dispatch Queue(调度队列)


队列是GCD的核心,用于存放任务,按「FIFO(先进先出)」原则执行任务。队列分为两类:

队列类型 特点 典型场景


3. 系统预定义队列


开发者无需手动创建所有队列,系统提供了3类常用队列:


• 主队列(Main Queue):

串行队列,仅在主线程执行,用于执行UI操作(如更新UI、刷新列表)。

获取方式:dispatch_get_main_queue()(iOS 10+ 可用 DispatchQueue.main)。


• 全局并发队列(Global Concurrent Queues):

系统提供的并发队列,有4个优先级(从高到低):

DISPATCH_QUEUE_PRIORITY_HIGH、DEFAULT、LOW、BACKGROUND(iOS 8+ 统一为 qos 质量服务等级,如 .userInitiated、.utility 等)。

获取方式:dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)(或 DispatchQueue.global())。


• 自定义队列:

开发者可通过 dispatch_queue_create 创建串行/并发队列(指定 DISPATCH_QUEUE_SERIAL 或 DISPATCH_QUEUE_CONCURRENT)。


4. 任务执行方式:同步(sync)vs 异步(async)


任务通过「同步」或「异步」方式提交到队列,决定是否阻塞当前线程:


• 同步(sync):

提交任务后,当前线程会等待任务执行完成才继续往下走(阻塞当前线程)。

用法:dispatch_sync(queue, ^{ 任务代码 });


• 异步(async):

提交任务后,当前线程立即返回,不等待任务执行(非阻塞)。

用法:dispatch_async(queue, ^{ 任务代码 });


5. 关键组合:队列类型 + 执行方式


不同队列与同步/异步的组合,决定任务的执行线程和是否阻塞,这是GCD的核心难点:



死锁案例:在主线程调用主队列的同步执行,会导致死锁。

原因:主队列是串行队列,当前主线程正在执行任务A,同步提交的任务B需等待A完成;但A又在等待B完成(因为sync会阻塞A),形成循环等待。

// 主线程执行以下代码会立即死锁

dispatch_sync(dispatch_get_main_queue(), ^{

    NSLog(@"任务B"); // 永远不会执行

});

6. GCD高级特性


除了基础的队列和任务,GCD还提供了多个工具类API解决复杂场景:


• Dispatch Group(调度组):监听多个任务(可在不同队列)的完成状态,所有任务完成后执行回调。

核心API:dispatch_group_create()、dispatch_group_enter()/leave()(手动标记任务开始/结束)、dispatch_group_notify()(任务全部完成后回调)。


• Dispatch Semaphore(信号量):控制并发数(类似「线程锁」),通过信号量计数限制同时执行的任务数。

核心API:dispatch_semaphore_create(initialValue)(初始信号量)、dispatch_semaphore_wait()(信号量-1,若为0则阻塞)、dispatch_semaphore_signal()(信号量+1)。


• Dispatch Barrier(栅栏):在并发队列中,保证栅栏任务前的所有任务执行完后,再执行栅栏任务,且栅栏任务执行完后才执行后续任务(解决「读写安全」问题)。

核心API:dispatch_barrier_async(queue, ^{ 栅栏任务 });(仅对自定义并发队列有效,全局并发队列无效)。


• dispatch_once:保证代码块仅执行一次(线程安全),常用于单例模式。

用法:static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ 初始化代码 });


• dispatch_after:延迟指定时间后执行任务(注意:是「延迟提交任务」,非「延迟执行完成」)。


二、多线程实际案例


案例1:网络请求后更新UI(必须在主队列)


场景:子线程执行网络请求(耗时),请求完成后需更新UI(如刷新列表),而UI操作必须在主线程执行。

方案:子线程执行网络请求,完成后通过 dispatch_async 提交UI更新任务到主队列。

// 子线程执行网络请求(异步+并发队列)

dispatch_async(dispatch_get_global_queue(0, 0), ^{

    // 1. 子线程执行耗时网络请求

    NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://api.example.com/data"]];

    NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];

   

    // 2. 网络请求完成后,提交UI更新任务到主队列

    dispatch_async(dispatch_get_main_queue(), ^{

        self.label.text = result[@"content"]; // UI操作必须在主队列

        [self.tableView reloadData];

    });

});

案例2:多任务并行执行后汇总结果(Group)


场景:需要同时请求3个接口(如用户信息、商品列表、消息通知),全部请求完成后汇总数据并刷新页面。

方案:用并发队列+Group,所有任务完成后通过 group_notify 回调汇总结果。

// 1. 创建调度组和并发队列

dispatch_group_t group = dispatch_group_create();

dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.concurrent", DISPATCH_QUEUE_CONCURRENT);


// 2. 定义存储结果的变量(需线程安全,这里简化用原子属性)

__block NSDictionary *userInfo = nil;

__block NSArray *goodsList = nil;

__block NSArray *notifications = nil;


// 3. 添加任务1到组(用户信息请求)

dispatch_group_enter(group); // 手动标记任务开始

dispatch_async(concurrentQueue, ^{

    userInfo = [self fetchUserInfo]; // 耗时操作

    dispatch_group_leave(group); // 任务完成,标记结束

});


// 4. 添加任务2到组(商品列表请求)

dispatch_group_enter(group);

dispatch_async(concurrentQueue, ^{

    goodsList = [self fetchGoodsList];

    dispatch_group_leave(group);

});


// 5. 添加任务3到组(消息通知请求)

dispatch_group_enter(group);

dispatch_async(concurrentQueue, ^{

    notifications = [self fetchNotifications];

    dispatch_group_leave(group);

});


// 6. 所有任务完成后,在主队列更新UI

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

    NSLog(@"所有请求完成:%@, %@, %@", userInfo, goodsList, notifications);

    [self.refreshControl endRefreshing]; // 更新UI

});

案例3:控制并发数(防止线程爆炸)


场景:同时下载100个文件,若不限定并发数,系统可能创建大量线程导致内存飙升或CPU占用过高。需控制最多同时下载3个文件。

方案:用信号量(初始值=3),每个下载任务开始前「等待信号量」(-1),完成后「发送信号量」(+1),保证同时最多3个任务执行。

// 1. 创建信号量(初始值=3,最多3个并发)

dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);

// 2. 创建并发队列

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


// 3. 模拟100个下载任务

for (int i = 0; i < 100; i++) {

    dispatch_async(queue, ^{

        // 等待信号量(信号量-1,若为0则阻塞等待)

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

       

        // 执行下载任务(耗时操作)

        NSLog(@"开始下载第%d个文件,当前线程:%@", i, [NSThread currentThread]);

        [NSThread sleepForTimeInterval:2]; // 模拟下载耗时

        NSLog(@"完成下载第%d个文件", i);

       

        // 发送信号量(信号量+1,唤醒等待的任务)

        dispatch_semaphore_signal(semaphore);

    });

}

案例4:单例模式的线程安全实现


场景:单例需保证全局唯一实例,且在多线程同时初始化时不创建多个实例。

方案:用 dispatch_once,无论多少线程同时调用,代码块仅执行一次,绝对线程安全。

@implementation SingletonManager

+ (instancetype)sharedInstance {

    static SingletonManager *instance = nil;

    static dispatch_once_t onceToken; // 仅初始化一次的标记

    dispatch_once(&onceToken, ^{ // 线程安全,保证代码块只执行一次

        instance = [[SingletonManager alloc] init];

    });

    return instance;

}

@end

案例5:文件读写的线程安全(读写分离)


场景:多个线程可能同时读取文件,偶尔有线程写入文件。需保证「读操作可并行,写操作必须独占(写时不能读,读时不能写)」。

方案:用自定义并发队列+栅栏(barrier),读操作正常提交,写操作通过栅栏提交,保证写时独占队列。

// 1. 创建自定义并发队列(全局并发队列不支持栅栏)

dispatch_queue_t fileQueue = dispatch_queue_create("com.example.file", DISPATCH_QUEUE_CONCURRENT);


// 2. 读操作(可并行)

- (NSString *)readFile {

    __block NSString *content = nil;

    dispatch_sync(fileQueue, ^{ // 同步执行,等待结果返回

        content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];

    });

    return content;

}


// 3. 写操作(栅栏保证独占,写前所有读/写完成,写后再执行后续操作)

- (void)writeFile:(NSString *)content {

    dispatch_barrier_async(fileQueue, ^{ // 栅栏任务,独占队列

        [content writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];

    });

}

效果:多个读操作可同时执行,写操作会等待所有之前的读/写完成后执行,且写操作执行时,后续的读/写会等待,保证文件读写安全。


三、总结


GCD的核心是「队列(管理任务顺序)」和「任务(执行逻辑)」,通过同步/异步执行方式控制线程行为,结合Group、Semaphore、Barrier等工具解决多线程协同问题。实际开发中需注意:


• UI操作必须在主队列执行,否则会崩溃;


• 避免主队列同步执行导致死锁;


• 控制并发数防止线程爆炸;


• 用合适的工具(如栅栏、信号量)保证线程安全。


掌握GCD可大幅简化多线程开发,提升程序性能和稳定性。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容