引言 在iOS开发领域,多线程编程是优化应用性能与响应能力的关键技术。而Grand Central Dispatch(GCD)作为苹果推出的强大且高效的多线程解决方案,以其卓越的性能和易用性,成为开发者处理多线程任务的首选。本文将深入剖析GCD的底层原理、核心概念、常用API及其丰富多样的应用场景,并结合大量示例代码进行详细阐述。
一、GCD 基础概念 1.1 什么是 GCD GCD 是基于 C 语言的多线程技术,它对线程管理进行了高度抽象和封装。开发者无需手动处理线程的创建、销毁和调度等复杂操作,只需将任务封装成 Block 并提交到合适的队列中,GCD 会自动完成后续的线程管理工作,大大简化了多线程编程的复杂度。
1.2 任务与队列
任务 :指的是要执行的具体工作,在 GCD 中使用 Block 来表示。Block 是一种闭包,可以捕获上下文变量,方便进行代码的封装和传递。
队列 :是用于存放任务的容器,根据任务执行的方式不同,可分为串行队列和并发队列。
1.3 同步与异步执行
同步执行 :任务会在当前线程中执行,并且会阻塞当前线程,直到任务执行完成后,才会继续执行后续的代码。
异步执行 :会开启新的线程(如果有必要)来执行任务,不会阻塞当前线程,当前线程会继续执行后续的代码。
二、GCD 的核心组件 2.1 队列类型 2.1.1 串行队列 每次只能执行一个任务,只有当前一个任务执行完成后,才会开始执行下一个任务。可以通过 dispatch_queue_create
函数创建自定义的串行队列。
1 2 3 4 5 6 7 8 9 10 11 12 13 // 创建自定义串行队列 dispatch_queue_t serialQueue = dispatch_queue_create("com.example.serialQueue", DISPATCH_QUEUE_SERIAL); // 异步执行任务 dispatch_async(serialQueue, ^{ NSLog(@"Task 1 in serial queue"); sleep(1); // 模拟耗时操作 }); dispatch_async(serialQueue, ^{ NSLog(@"Task 2 in serial queue"); sleep(1); // 模拟耗时操作 });
2.1.2 并发队列 可以同时执行多个任务,任务的开始时间由 GCD 调度决定,但完成顺序不确定。可以通过 dispatch_queue_create
函数创建自定义的并发队列,也可以使用系统提供的全局并发队列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // 创建自定义并发队列 dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.concurrentQueue", DISPATCH_QUEUE_CONCURRENT); // 异步执行任务 dispatch_async(concurrentQueue, ^{ NSLog(@"Task 1 in concurrent queue"); sleep(1); // 模拟耗时操作 }); dispatch_async(concurrentQueue, ^{ NSLog(@"Task 2 in concurrent queue"); sleep(1); // 模拟耗时操作 }); // 获取全局并发队列 dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(globalQueue, ^{ NSLog(@"Task in global concurrent queue"); sleep(1); // 模拟耗时操作 });
2.1.3 主队列 是一个特殊的串行队列,专门用于在主线程上执行任务,通常用于更新 UI。可以通过 dispatch_get_main_queue
函数获取主队列。
1 2 3 4 5 6 7 // 异步执行任务到主队列 dispatch_async(dispatch_get_main_queue(), ^{ // 更新 UI UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 30)]; label.text = @"Hello, GCD!"; [self.view addSubview:label]; });
2.2 任务执行方式 2.2.1 同步执行(dispatch_sync
) 1 2 3 4 5 6 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_sync(queue, ^{ NSLog(@"Synchronous task"); sleep(1); // 模拟耗时操作 }); NSLog(@"This line will be executed after the synchronous task");
2.2.2 异步执行(dispatch_async
) 1 2 3 4 5 6 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ NSLog(@"Asynchronous task"); sleep(1); // 模拟耗时操作 }); NSLog(@"This line will be executed immediately without waiting for the asynchronous task");
2.3 延迟执行(dispatch_after
) 用于在指定的时间后执行任务,通常用于实现定时任务。
1 2 3 4 dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)); dispatch_after(delayTime, dispatch_get_main_queue(), ^{ NSLog(@"Delayed task executed after 2 seconds"); });
2.4 一次性执行(dispatch_once
) 确保代码块只执行一次,常用于实现单例模式,保证在多线程环境下单例对象的唯一性。
1 2 3 4 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSLog(@"This code block will be executed only once"); });
2.5 分组执行(dispatch_group
) 可以将多个任务分组,当组内所有任务完成后,执行特定的操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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, ^{ NSLog(@"Task 1 in group"); sleep(1); // 模拟耗时操作 }); dispatch_group_async(group, queue, ^{ NSLog(@"Task 2 in group"); sleep(1); // 模拟耗时操作 }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"All tasks in the group are completed"); });
2.6 栅栏函数(dispatch_barrier_async
和 dispatch_barrier_sync
) 用于在并发队列中实现同步操作,确保在栅栏函数之前的任务完成后,才会执行栅栏函数及其后面的任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.concurrentQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(concurrentQueue, ^{ NSLog(@"Task 1 before barrier"); sleep(1); // 模拟耗时操作 }); dispatch_async(concurrentQueue, ^{ NSLog(@"Task 2 before barrier"); sleep(1); // 模拟耗时操作 }); dispatch_barrier_async(concurrentQueue, ^{ NSLog(@"Barrier task"); sleep(1); // 模拟耗时操作 }); dispatch_async(concurrentQueue, ^{ NSLog(@"Task 1 after barrier"); sleep(1); // 模拟耗时操作 }); dispatch_async(concurrentQueue, ^{ NSLog(@"Task 2 after barrier"); sleep(1); // 模拟耗时操作 });
2.7 信号量(dispatch_semaphore
) 用于控制并发任务的数量,通过信号量的计数来限制同时执行的任务数。
1 2 3 4 5 6 7 8 9 10 11 dispatch_semaphore_t semaphore = dispatch_semaphore_create(2); // 允许同时执行 2 个任务 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); for (int i = 0; i < 5; i++) { dispatch_async(queue, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"Task %d is executing", i); sleep(1); // 模拟耗时操作 dispatch_semaphore_signal(semaphore); }); }
三、GCD 的底层原理 3.1 线程池管理 GCD 维护了一个高效的线程池,用于管理线程的创建、复用和销毁。线程池的大小会根据系统的负载和任务的需求动态调整。当有任务提交到队列时,GCD 会从线程池中获取可用的线程来执行任务。如果线程池中没有可用的线程,且当前线程数量未达到系统限制,GCD 会创建新的线程;如果达到系统限制,则任务会等待直到有线程可用。
线程池的优势在于避免了频繁创建和销毁线程带来的性能开销。线程的创建和销毁是一个相对昂贵的操作,会消耗大量的系统资源和时间。通过线程池,GCD 可以复用已有的线程,提高了线程的利用率,从而提升了系统的整体性能。
3.2 任务调度算法 GCD 使用了复杂而高效的任务调度算法,根据任务的优先级、队列类型和线程池的状态等因素,决定任务的执行顺序和线程分配。
3.2.1 优先级调度 GCD 中的任务可以有不同的优先级,全局并发队列提供了四个不同优先级的队列:高、默认、低和后台。高优先级的任务会优先被调度执行,但需要注意的是,过高的优先级可能会导致低优先级的任务长时间得不到执行,产生饥饿现象。
3.2.2 队列类型影响 串行队列中的任务会按照提交的顺序依次执行,而并发队列中的任务会根据系统资源和调度算法并行执行。在并发队列中,GCD 会尽量充分利用多核 CPU 的优势,同时执行多个任务,提高系统的并发处理能力。
3.2.3 线程池状态 线程池的状态也是任务调度的重要考虑因素。如果线程池中有空闲线程,GCD 会优先将任务分配给这些空闲线程;如果线程池已满,任务会被放入队列中等待。
3.3 锁机制 在多线程环境下,为了保证线程安全,GCD 使用了多种锁机制。例如,在访问共享资源时,会使用互斥锁来避免数据竞争。互斥锁确保同一时间只有一个线程可以访问共享资源,从而防止多个线程同时修改数据导致的数据不一致问题。
另外,GCD 还使用了读写锁来优化对共享资源的读写操作。读写锁允许多个线程同时进行读操作,但在进行写操作时,会独占资源,防止其他线程进行读写操作。这种机制可以提高并发性能,尤其是在读操作频繁的场景下。
3.4 工作队列与内核 GCD 的工作队列与内核紧密协作,通过内核的调度机制来实现任务的高效执行。当任务提交到队列后,GCD 会将任务封装成一个工作项,并将其添加到工作队列中。内核会根据系统的负载和调度策略,从工作队列中取出工作项并分配给合适的线程执行。
GCD 与内核的协作还体现在线程的创建和销毁上。当需要创建新线程时,GCD 会向内核请求资源,内核会分配相应的线程资源;当线程不再使用时,GCD 会通知内核释放这些资源。
四、GCD 的应用场景 4.1 网络请求 在开发中,网络请求通常是一个耗时的操作,如果在主线程中进行网络请求,会导致界面卡顿,影响用户体验。因此,我们通常会在子线程中进行网络请求,完成后回到主线程更新 UI。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 网络请求 NSURL *url = [NSURL URLWithString:@"https://example.com"]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; NSURLSession *session = [NSURLSession sharedSession]; NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (error) { NSLog(@"Network request error: %@", error.localizedDescription); } else { dispatch_async(dispatch_get_main_queue(), ^{ // 更新 UI NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 30)]; label.text = responseString; [self.view addSubview:label]; }); } }]; [task resume]; });
4.2 图片处理 图片处理,如解码、缩放、裁剪等操作,通常比较耗时。我们可以在子线程中进行这些操作,完成后回到主线程显示图片,避免阻塞主线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 加载图片 UIImage *image = [UIImage imageNamed:@"largeImage"]; // 图片处理 UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100), NO, 0.0); [image drawInRect:CGRectMake(0, 0, 100, 100)]; UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); dispatch_async(dispatch_get_main_queue(), ^{ // 显示图片 UIImageView *imageView = [[UIImageView alloc] initWithImage:scaledImage]; imageView.frame = CGRectMake(0, 0, 100, 100); [self.view addSubview:imageView]; }); });
4.3 数据处理 在处理大量数据时,如文件读写、数据库操作等,会消耗大量的时间和系统资源。我们可以将这些操作放在子线程中进行,避免阻塞主线程,提高应用的响应性能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 数据处理 NSString *filePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/largeFile.txt"]; NSString *fileContent = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; // 处理文件内容 NSArray *lines = [fileContent componentsSeparatedByString:@"\n"]; NSLog(@"Number of lines: %lu", (unsigned long)lines.count); dispatch_async(dispatch_get_main_queue(), ^{ // 更新 UI 或显示结果 UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 30)]; label.text = [NSString stringWithFormat:@"Number of lines: %lu", (unsigned long)lines.count]; [self.view addSubview:label]; }); });
4.4 后台任务 在某些情况下,我们需要在应用进入后台后继续执行一些任务,如上传文件、下载数据等。GCD 可以帮助我们实现后台任务的管理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 - (void)applicationDidEnterBackground:(UIApplication *)application { __block UIBackgroundTaskIdentifier backgroundTask = [application beginBackgroundTaskWithExpirationHandler:^{ [application endBackgroundTask:backgroundTask]; backgroundTask = UIBackgroundTaskInvalid; }]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 后台任务 for (int i = 0; i < 10; i++) { NSLog(@"Background task: %d", i); sleep(1); } [application endBackgroundTask:backgroundTask]; backgroundTask = UIBackgroundTaskInvalid; }); }
4.5 预加载数据 为了提高应用的响应速度,我们可以在应用启动或用户操作之前,提前加载一些数据。使用 GCD 可以在后台线程中进行数据的预加载,避免阻塞主线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 预加载数据 NSArray *data = [self loadDataFromServer]; // 将数据存储到本地或缓存中 [self cacheData:data]; }); return YES; } - (NSArray *)loadDataFromServer { // 模拟从服务器加载数据 sleep(3); return @[@"Data 1", @"Data 2", @"Data 3"]; } - (void)cacheData:(NSArray *)data { // 缓存数据 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; [defaults setObject:data forKey:@"preloadedData"]; [defaults synchronize]; }
4.6 并发计算 在进行复杂的计算任务时,如数学计算、图像处理算法等,可以利用 GCD 的并发队列将任务分解为多个子任务并行执行,提高计算效率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.concurrentCalculation", DISPATCH_QUEUE_CONCURRENT); __block int result = 0; dispatch_group_t group = dispatch_group_create(); // 分解任务 for (int i = 0; i < 10; i++) { dispatch_group_async(group, concurrentQueue, ^{ int partialResult = [self calculatePartialResult:i]; @synchronized(self) { result += partialResult; } }); } dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"Final result: %d", result); }); - (int)calculatePartialResult:(int)index { // 模拟复杂计算 sleep(1); return index * 2; }
五、GCD 的注意事项 5.1 死锁问题 在使用 dispatch_sync
时要特别注意死锁问题。例如,在主队列中同步执行主队列的任务会导致死锁,因为主线程会等待同步任务执行完成,而同步任务又在等待主线程空闲,从而形成死锁。
1 2 3 4 // 以下代码会导致死锁 dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"This code will cause a deadlock"); });
5.2 线程安全 在多线程环境下访问共享资源时,要确保线程安全。可以使用信号量、锁等机制来避免数据竞争和不一致问题。例如,在多个线程同时访问和修改一个数组时,需要使用锁来保证同一时间只有一个线程可以进行操作。
5.3 内存管理 在使用 GCD 时,要注意 Block 的内存管理,避免循环引用。Block 会捕获其上下文中的变量,如果不小心,可能会导致对象之间的循环引用,从而造成内存泄漏。可以使用弱引用(如 __weak
)来避免循环引用。
六、总结 GCD 是 iOS 开发中强大且高效的多线程技术,通过合理运用 GCD 的队列、任务执行方式和各种高级特性,开发者可以轻松实现多线程编程,提升应用的性能和响应能力。同时,在使用 GCD 时要注意死锁、线程安全和内存管理等问题,以确保代码的健壮性。希望本文能帮助你深入理解 GCD 的底层原理和应用场景,在实际开发中更好地运用 GCD 技术。
参考资料 :