iOS 多线程编程

153 阅读7分钟

1 多线程编程

  1. NSThread

    • 提供了更底层的线程控制能力,用于创建和管理线程对象。
    • 需要手动管理线程的生命周期和线程通信。
  2. GCD(Grand Central Dispatch)

    • 基于 C 语言开发的一套用于多线程编程的 API。
    • 自动管理线程的生命周期,包括任务调度、队列管理和线程管理。
  3. NSOperation 和 NSOperationQueue

    • 基于 GCD 的更高级别的抽象,提供了面向对象的方式来管理任务。
    • 通过继承 NSOperation 类或使用它的子类 NSBlockOperation 来创建自定义操作。
    • NSOperationQueue 是用来管理 NSOperation 对象的操作队列,系统会自动管理它们的执行顺序和线程分配。
    • NSOperationQueue 和 NSOperation 提供了更高级别的任务管理机制,例如依赖关系、取消操作、优先级和同步等。

2 NSThread

2.1 创建线程

1. 创建线程 

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(todo) object:nil]; 
// 启动线程 
[thread start];

2. 创建并自启动一个新的线程

[NSThread detachNewThreadSelector:@selector(todo) toTarget:self withObject:nil];

2.2 线程通信

#import <Foundation/Foundation.h>

@interface CustomThread : NSObject

@end

@implementation CustomThread 

- (void)threadMethod:(id)object {
    NSLog(@"子线程开始执行");
    
    // 模拟耗时操作
    [NSThread sleepForTimeInterval:2.0];
    
    // 在主线程上调用方法,实现线程通信
    [self performSelectorOnMainThread:@selector(updateUI) withObject:nil waitUntilDone:YES];
    
    NSLog(@"子线程结束执行");
}

- (void)updateUI {
    NSLog(@"更新UI操作在主线程上执行");
}

@end

// 具体使用
CustomThread *customThread = [[CustomThread alloc] init];
NSThread *thread = [[NSThread alloc] initWithTarget:customThread selector:@selector(threadMethod:) object:nil];
[thread start]; 

3 GCD(Grand Central Dispatch)

3.1 任务和队列

  1. 任务的执行方式
  • 同步:任务在当前线程中执行,会阻塞当前线程(不具备开启新线程的能力
  • 异步:任务在新的线程中执行,不会阻塞当前线程
  1. 队列的管理方式
  • 串行:任务按顺序依次执行
  • 并行:任务可以同时执行,不受顺序限制
  1. 任务和队列组合方式
  • 同步并行:任务在当前线程中按顺序依次执行
  • 异步串行:创建一条新线程,任务在新线程中按顺序依次执行
  • 异步并行:创建多条新线程,任务同时执行
  • 同步主队列:死锁
  • 异步主队列:同异步串行,但不创建新线程,任务在主线程中按顺序依次执行

主队列是一个特殊的串行队列,通常用于在主线程上执行任务。

同步主队列:在主队列中执行的任务需要在主线程上执行,而同步任务会阻塞当前线程直到任务完成,所以会出现死锁问题。

异步主队列:系统会将任务放入一个全局共享的后台并发队列中,这个队列会自动管理任务的执行,而不需要专门创建新的线程。

3.2 创建任务和队列

1. 同步任务

dispatch_sync(queue, ^{
    // TODO
});

2. 异步任务

dispatch_async(queue, ^{
    // TODO
});

3. 串行队列

dispatch_queue_t queue = dispatch_queue_create("com.example.serialqueue", DISPATCH_QUEUE_SERIAL);

4. 并行队列

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

5. 获取主队列

dispatch_queue_t queue = dispatch_get_main_queue()

6. 获取全局并发队列

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

3.3 常用方法

1.dispatch_once:确保代码块在程序运行过程中只会执行一次,通常用于单例模式的实现。

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只会执行一次的代码块
}); 

2.dispatch_after:延迟一段时间后执行任务。

dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));
dispatch_after(delay, dispatch_get_main_queue(), ^{
    // 2秒后在主队列执行的任务
}); 

3. dispatch_apply:在指定的队列上多次执行指定的任务,可以用于遍历集合或执行重复性任务。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(5, queue, ^(size_t index) {
    // 执行5次任务
}); 

4. dispatch_group_notify:当一组任务执行完成后,执行特定的任务。

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, ^{
    // Task 1
});

dispatch_group_async(group, queue, ^{
    // Task 2
});

// 等待 dispatch group 中的所有任务完成
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

dispatch_group_async(group, queue, ^{
    // Task 3
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 所有任务执行完成后在主队列执行的任务
    NSLog(@"All tasks completed");
}); 

使用 dispatch_group_enterdispatch_group_leave 动态地管理任务的数量。

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

// 往 dispatch group 中添加任务
dispatch_group_enter(group);
dispatch_async(queue, ^{
    NSLog(@"Task 1 started");
    // 模拟耗时任务
    sleep(2);
    NSLog(@"Task 1 finished");

    // 任务完成后离开 dispatch group
    dispatch_group_leave(group);
});

// 往 dispatch group 中添加另一个任务
dispatch_group_enter(group);
dispatch_async(queue, ^{
    NSLog(@"Task 2 started");
    // 模拟耗时任务
    sleep(3);
    NSLog(@"Task 2 finished");

    // 任务完成后离开 dispatch group
    dispatch_group_leave(group);
});

// 等待所有任务完成
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"All tasks have completed");
    // 在这里可以进行一些操作,例如更新 UI 界面
}); 

5. dispatch_barrier_async:在并发队列中插入一个栅栏,确保在它之前提交的任务执行完成后再执行,但是在它之后提交的任务需要等待它执行完成。

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

dispatch_async(queue, ^{    // Task 1
});

dispatch_async(queue, ^{    // Task 2
});

dispatch_barrier_async(queue, ^{    // Barrier task
    // 确保在此之前的任务执行完成后才执行,之后的任务需要等待
});

dispatch_async(queue, ^{    // Task 3
});

dispatch_async(queue, ^{    // Task 4
}); 

6. dispatch_semaphore_create 和 dispatch_semaphore_wait:使用信号量实现线程同步。

// 创建一个初始值为0的信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"Task A started");
    // 模拟耗时任务
    sleep(2);
    NSLog(@"Task A finished");
    
    // 发出信号量,允许 Task B 执行
    dispatch_semaphore_signal(semaphore);
});

NSLog(@"Task B waiting");
// 等待信号量
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"Task B started");
    // 模拟耗时任务
    sleep(2);
    NSLog(@"Task B finished");
}); 

3.4 线程通信

- (void)communication {
    // 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 异步执行任务
    dispatch_async(queue, ^{

        // 模拟耗时操作
        for (int i = 0; i < 2; i++) {
            // TODO
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            // 回到主线程更新 UI
        });
    });
}

4 NSOperation 和 NSOperationQueue

4.1 NSOperation

NSOperation 是 Foundation 框架中的一个抽象类,不能直接使用,可以通过其子类 NSBlockOperation 和 NSInvocationOperation 来创建操作,或者自定义一个继承自 NSOperation 的子类。

1. NSBlockOperation

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{    
    // 执行操作
}];

[operation addExecutionBlock:^{    
    // 添加额外的执行操作任务,同一个操作,两个任务
}];

// 执行操作,默认同步执行
[operation start];

NSOperation 调用 start 方法默认是同步执行,会在当前线程中执行任务,而不会开启新的线程。
NSOperation 对象添加到 NSOperationQueue 中执行,系统会自动异步执行操作,无需手动调用 start 方法。
NSBlockOperation 封装的操作数量大于 1 时,无论是否使用 NSOperationQueue,NSBlockOperation 都会以并发的方式异步执行这些操作。

2. NSInvocationOperation

NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(runOperation) object:nil];
[operation start];

3. 自定义子类

@interface MyCustomOperation : NSOperation

// 可以添加自定义的属性,用于传递参数等
@property (readonly, getter=isCancelled) BOOL cancelled;

@end

@implementation MyCustomOperation

- (void)main {
    @autoreleasepool {
        // 在这里执行任务逻辑
        
        // 检查是否被取消
        if (self.isCancelled) {
            return;
        }
        
        // 执行任务...
        
        // 完成任务后,可以通过调用完成的代码块或通知等方式来通知其他部分
    }
}

@end

4.2 NSOperationQueue

NSOperationQueue 是一个用于管理 NSOperation 对象的操作队列。将 NSOperation 对象添加到 NSOperationQueue 中,可以实现对操作的异步执行、并发控制、操作依赖管理等功能。

  1. 异步执行: 将 NSOperation 添加到 NSOperationQueue 中后,系统会自动在合适的时机异步执行这些操作,无需手动调用 start 方法。NSOperationQueue 会根据队列的类型(串行队列或并发队列)来确定操作的执行方式。
  2. 并发控制: NSOperationQueue 可以用来控制操作的并发数量,可以创建串行队列(NSOperationQueue.serial)实现串行执行,也可以创建并发队列(NSOperationQueue.concurrent)实现并发执行。
  3. 操作依赖管理: 可以通过添加操作依赖关系(addDependency:)来确保某些操作在其他操作执行完毕后再执行,NSOperationQueue 会自动处理依赖关系,保证操作的执行顺序满足依赖关系。
  4. 线程管理: NSOperationQueue 会自动管理执行操作所需的线程,无需手动创建和管理线程。

1. 操作队列类型

主队列:主队列是一个全局的操作队列,用于在主线程上执行操作。

NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];

自定义队列:通过创建自定义的 NSOperationQueue 对象来管理操作。

NSOperationQueue *customQueue = [[NSOperationQueue alloc] init];

2. 操作队列方法

  • addOperation: 向队列中添加操作
  • addOperations:waitUntilFinished: 添加多个操作,并选择是否等待它们全部执行完毕
  • addOperationWithBlock: 添加一个使用 block 来定义的操作
  • cancelAllOperations 取消队列中的所有操作,操作对象的 cancel 方法可以取消单个操作
  • setSuspended: 暂停和恢复队列

3. 操作依赖管理

// 创建 NSOperationQueue 对象
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

// 创建两个自定义操作
NSOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    // 执行操作1的任务
}];

NSOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    // 执行操作2的任务
}];

// 将操作2添加为操作1的依赖,操作2依赖于操作1,因此只有在操作1完成后,操作2才能执行
[operation2 addDependency:operation1];

// 将操作添加到操作队列中执行
[queue addOperation:operation1];
[queue addOperation:operation2]; 

4. 如何取消任务执行?

调用操作对象 cancel 方法并不能使操作马上停止执行。

当 NSOperation 的 cancel 方法被调用后,如果操作不在队列中,这个方法会将操作的 isFinished 设为YES,如果在操作队列中,这个方法会将操作对象的 isCancelled 状态设为YES,并且 isReady 设为YES,让队列调用它的 start 方法。在 start 或者 main 方法实现中,我们应该检查 isCancelledisFinished 属性,如果任意一个为YES,就不执行操作,直接返回,如果是并发操作,让 isFinished 方法返回YES,如果是非并发操作,设置isFinished 值为YES。