MST

星途 面试题库

面试题:Objective-C块(Block)在多线程及GCD中的高级应用及优化

在多线程编程以及Grand Central Dispatch(GCD)框架下,块(Block)有着广泛应用。请深入分析块在这些场景中的高级应用技巧,比如如何利用块进行高效的异步任务调度、如何避免多线程环境下块使用时的资源竞争问题,同时针对可能出现的性能瓶颈提出优化策略并给出具体代码示例。
17.1万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

1. 利用块进行高效的异步任务调度

  • GCD 异步调度基础: 在 GCD 中,dispatch_async 函数用于将任务提交到指定队列并异步执行。例如,将一个耗时的网络请求任务提交到全局队列:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 网络请求代码
    NSURL *url = [NSURL URLWithString:@"https://example.com/api"];
    NSData *data = [NSData dataWithContentsOfURL:url];
    // 处理数据
    dispatch_async(dispatch_get_main_queue(), ^{
        // 更新 UI 等操作
    });
});
  • 队列优先级与任务控制: GCD 提供不同优先级的队列,如 DISPATCH_QUEUE_PRIORITY_HIGHDISPATCH_QUEUE_PRIORITY_DEFAULTDISPATCH_QUEUE_PRIORITY_LOWDISPATCH_QUEUE_PRIORITY_BACKGROUND。可以根据任务的重要性和对性能的影响来选择合适的队列。例如,对于一些不太紧急的后台数据处理任务,可以使用低优先级队列:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
    // 后台数据处理
});
  • 组(Group)的使用: 当需要等待一组异步任务完成后再执行后续操作时,可以使用 dispatch_group。例如,同时发起多个网络请求,待所有请求完成后进行数据合并处理:
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, ^{
    // 第一个网络请求
    NSURL *url1 = [NSURL URLWithString:@"https://example.com/api1"];
    NSData *data1 = [NSData dataWithContentsOfURL:url1];
});

dispatch_group_async(group, queue, ^{
    // 第二个网络请求
    NSURL *url2 = [NSURL URLWithString:@"https://example.com/api2"];
    NSData *data2 = [NSURL URLWithString:url2];
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 合并数据并更新 UI
});

2. 避免多线程环境下块使用时的资源竞争问题

  • 使用串行队列: 如果多个任务访问共享资源,将这些任务提交到同一个串行队列中,这样可以保证任务顺序执行,避免资源竞争。例如,有一个共享的计数器:
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.serialQueue", DISPATCH_QUEUE_SERIAL);
__block int counter = 0;

dispatch_async(serialQueue, ^{
    counter++;
});

dispatch_async(serialQueue, ^{
    counter--;
});
  • 锁机制: 在多线程环境下,使用锁来保护共享资源。以 NSLock 为例:
NSLock *lock = [[NSLock alloc] init];
__block int sharedValue = 0;

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [lock lock];
    sharedValue++;
    [lock unlock];
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [lock lock];
    sharedValue--;
    [lock unlock];
});
  • 原子属性: 如果共享资源是对象的属性,可以将属性声明为原子(atomic)的。例如:
@interface MyClass : NSObject
@property (nonatomic, atomic, strong) NSString *sharedString;
@end

@implementation MyClass
@end

这样在多线程环境下访问 sharedString 时,会自动进行线程安全的操作,但原子属性会带来一定的性能开销。

3. 性能瓶颈及优化策略

  • 性能瓶颈
    • 频繁创建和销毁队列:频繁创建和销毁队列会带来额外的开销。
    • 锁竞争:如果锁的粒度太大或持有锁的时间过长,会导致线程等待,降低性能。
    • 过度的异步任务:过多的异步任务可能导致系统资源耗尽,尤其是在内存和 CPU 方面。
  • 优化策略
    • 复用队列:尽量复用系统提供的全局队列或创建少量的自定义队列,而不是频繁创建和销毁。
    • 减小锁粒度:只在访问共享资源的关键代码段加锁,并且尽量缩短持有锁的时间。例如:
NSLock *lock = [[NSLock alloc] init];
__block int sharedValue = 0;

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    int temp = 0;
    // 先在无锁状态下进行部分计算
    temp = sharedValue + 1;
    [lock lock];
    sharedValue = temp;
    [lock unlock];
});
- **控制异步任务数量**:可以使用信号量(`dispatch_semaphore`)来控制同时执行的异步任务数量。例如,限制同时最多有 3 个网络请求在执行:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
NSArray *urls = @[@"https://example.com/api1", @"https://example.com/api2", @"https://example.com/api3", @"https://example.com/api4"];

for (NSString *urlString in urls) {
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSURL *url = [NSURL URLWithString:urlString];
        NSData *data = [NSData dataWithContentsOfURL:url];
        dispatch_semaphore_signal(semaphore);
    });
}