iOS开发-多线程编程
OC中常用的多线程编程技术:
1. NSThread
NSThread
是Objective-C中最基本的线程抽象,它允许程序员直接管理线程的生命周期。
NSThread *myThread = [[NSThread alloc] initWithTarget:self selector:@selector(myThreadMainMethod:) object:nil];
[myThread start];
使用NSThread
时,需要自己管理线程的生命周期,包括创建、启动和销毁线程。这种方法给了开发者很大的控制权,但也增加了复杂性,因为需要手动处理线程同步和线程安全问题。
2. Grand Central Dispatch (GCD)
GCD是一个强大的基于C语言的API,它提供了一个并发执行任务的低级别方式。GCD使用任务队列和线程池来优化线程的使用。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
// 异步执行的任务
});
GCD是推荐的多线程编程方法之一,因为它的性能很好,而且简化了并发编程的复杂性(下一节详细介绍)。
3. Operation Queues
NSOperation
和NSOperationQueue
提供了一个面向对象的方式来执行并发操作。NSOperation
是一个抽象类,可以通过继承它来定义具体的操作。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(myTaskMethod) object:nil];
[queue addOperation:operation];
NSOperationQueue
可以管理多个NSOperation
对象,它支持设置最大并发操作数和依赖关系。NSOperation
比GCD更高级,它支持取消操作、设置操作依赖和观察操作状态。
4. Perform Selector Methods
Objective-C提供了performSelector:onThread:
等方法来在指定的线程上执行方法。
[self performSelector:@selector(myTaskMethod) onThread:myThread withObject:nil waitUntilDone:NO];
这些方法简单易用,但它们不提供GCD和NSOperation
的强大功能。
5. POSIX Threads (pthreads)
POSIX线程是一套跨平台的线程相关的API,Objective-C可以直接使用这些API,因为它是C语言的超集。
#include <pthread.h>
void *myThreadFunction(void *context) {
// 线程执行的代码
return NULL;
}
pthread_t thread;
pthread_create(&thread, NULL, myThreadFunction, NULL);
pthreads
提供了很大的控制力,但它是一个低级别的API,通常不推荐在OC中使用,除非需要与C库交互或有特殊的线程管理需求。
GCD的详细介绍
Grand Central Dispatch (GCD) 是 Apple 开发的一个强大的多核编程解决方案,它提供了一种简单且高效的方式来管理并发任务。GCD 使用任务(blocks of code)和队列(queues)的概念来执行工作。它是基于 C 语言实现的,可以在 Objective-C 和 Swift 中使用。
核心概念
任务
任务是指要执行的工作,通常是以 block 的形式提供。在 Objective-C 中,一个任务可以是一个 block 或者一个函数。
队列
队列是一种特殊的数据结构,用于按顺序存储任务。GCD 提供了两种类型的队列:
- 串行队列(Serial Queue):一次只执行一个任务。队列中的任务按照添加的顺序依次执行。在内部实现了一种机制,确保队列中的任务一次只能执行一个,并且按照它们被添加到队列中的顺序来执行。这是通过队列管理和任务调度来实现的。
当将一个任务提交到串行队列时,这里是大致的工作流程:
任务排队:任务被添加到队列的末尾。如果队列是空的,它会成为队列中的第一个任务。
任务执行:队列中的第一个任务(队头)被取出来执行。在这个任务执行期间,队列不会执行或者开始执行任何其他任务。
任务完成:一旦当前执行的任务完成,它会从队列中移除。
下一个任务:队列中的下一个任务(现在是队头)开始执行。
重复过程:这个过程会一直重复,直到队列中的所有任务都被执行完毕。
串行队列的关键特性是互斥执行,这意味着在任何给定时间点,队列中只有一个任务在执行。这种互斥是由GCD的底层调度机制保证的,它确保了即使在多核处理器上,串行队列上的任务也不会并行执行。
这种执行方式使得串行队列成为了同步执行任务的理想选择,特别是需要按顺序执行一系列任务,而这些任务又不能同时执行时(例如,当任务需要按特定顺序访问或修改共享资源时)。因此理论上一个线程就足够了。这是串行队列如何保证任务顺序执行和互斥的关键。当一个任务在串行队列中开始执行时,它会持续运行直到完成,然后队列才会执行下一个任务。这个过程不需要同时有多个线程参与,因为不会有并行执行的情况。然而,实际上,由于GCD的工作原理,它可能会在内部使用多个线程来管理多个串行队列。GCD使用线程池来优化线程的使用,这意味着它会根据需要动态地为队列分配和回收线程。但对于任何单一的串行队列来说,你可以认为它在任何时候都只在一个线程上执行任务。这种设计使得串行队列成为管理共享资源和避免并发问题的理想工具,因为它简化了同步和线程安全的需求。同时,它也减少了上下文切换的开销,因为任务是在单个线程上连续执行的。
- 并行队列(Concurrent Queue):可以同时执行多个任务。任务可以并发执行,但完成的顺序可能会不同。
系统队列
GCD 提供了几种不同类型的系统队列:
- 主队列(Main Queue):串行队列,用于在主线程上执行任务,通常用于更新 UI。
- 全局队列(Global Queues):并行队列,有四个不同优先级的全局队列:高、默认、低和后台。
Grand Central Dispatch (GCD) 提供了几种不同类型的系统队列,这些队列是预先创建好的,可以直接使用。它们分为两大类:主队列(Main Queue)和全局队列(Global Queues)。
主队列(Main Queue)
- 主队列是一个特殊的串行队列,它在应用程序的主线程上执行任务。因为主线程通常用于更新UI,所以所有的UI更新都应该在主队列上执行,以确保UI的平滑和响应性。
- 使用
dispatch_get_main_queue()
函数可以获取主队列。
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
// 在主线程上更新UI
});
全局队列(Global Queues)
- 全局队列是并行队列,它们在后台执行任务,不会阻塞主线程。全局队列有四个不同的优先级:高(high)、默认(default)、低(low)和后台(background)。这些优先级对应于系统为任务分配的相对重要性。
- 使用
dispatch_get_global_queue()
函数可以获取全局队列,需要指定优先级和一个保留用的标志位(目前应该传递0
)。
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
// 在后台执行耗时任务
});
全局队列的优先级:
- 高优先级(
DISPATCH_QUEUE_PRIORITY_HIGH
):用于需要立即执行的任务,但不应该阻塞主线程。 - 默认优先级(
DISPATCH_QUEUE_PRIORITY_DEFAULT
):用于大多数任务,如果没有特殊的优先级要求,应该使用这个优先级。 - 低优先级(
DISPATCH_QUEUE_PRIORITY_LOW
):用于不急迫的任务,可以等待其他更重要的任务完成后再执行。 - 后台优先级(
DISPATCH_QUEUE_PRIORITY_BACKGROUND
):用于那些用户不太可能立即注意到的任务,如预取数据、维护或清理工作。
注意以下事项:
- 尽管全局队列是并行队列,但是任务的启动顺序仍然是按照它们被添加到队列的顺序。
- 全局队列不保证任务完成的顺序,任务可以并发执行。
- 主队列保证任务按照添加的顺序一个接一个地执行。
- 在主队列上同步执行任务会导致死锁,因为主队列等待同步任务完成,而同步任务又在等待主队列可用,从而形成了相互等待的情况。
自定义队列
除了系统队列,GCD还允许创建自定义队列。自定义队列可以是串行的也可以是并行的。
- 创建串行队列:
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.mySerialQueue", DISPATCH_QUEUE_SERIAL);
- 创建并行队列:
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
使用 GCD
异步执行
使用 dispatch_async
函数可以异步地将任务提交到队列中。这意味着它不会等待任务完成,而是立即返回。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
// 执行耗时的任务
});
同步执行
使用 dispatch_sync
函数可以同步地将任务提交到队列中。这会阻塞当前线程,直到任务执行完成。
dispatch_sync(queue, ^{
// 执行任务
});
延迟执行
使用 dispatch_after
函数可以在指定的时间后异步执行任务。
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^{
// 2秒后执行的任务
});
一次性执行
使用 dispatch_once
函数可以确保代码块只被执行一次,常用于创建单例。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行一次的代码
});
队列组
dispatch_group
允许多个任务作为一个组来提交,并在组中的所有任务完成时得到通知。
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
// 任务1
});
dispatch_group_async(group, queue, ^{
// 任务2
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 所有任务完成后执行
});
信号量
dispatch_semaphore
用于控制访问资源的线程数量,可以用来实现线程同步。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 访问受限资源
dispatch_semaphore_signal(semaphore);
});
注意事项
- 不要在串行队列上同步地执行任务,这可能会导致死锁。
- 尽量避免在主队列上同步执行耗时任务,这会阻塞 UI 更新。
- 使用 GCD 时,要注意内存管理,特别是在 block 中捕获外部变量时。