MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C多线程编程中的性能优化技巧

2021-01-287.2k 阅读

多线程编程基础概述

在Objective-C开发中,多线程编程旨在利用多核处理器的优势,提高应用程序的性能和响应性。多线程允许应用程序同时执行多个任务,避免主线程阻塞,确保用户界面的流畅交互。

多线程编程引入了一些挑战,比如资源竞争、死锁和线程安全问题。资源竞争发生在多个线程同时访问和修改共享资源时,可能导致数据不一致。死锁是指两个或多个线程相互等待对方释放资源,从而造成程序无法继续执行。

线程同步机制

为了解决多线程编程中的问题,Objective-C提供了多种线程同步机制。

  1. 互斥锁(Mutex):互斥锁是一种基本的同步工具,用于保证在同一时间只有一个线程可以访问共享资源。在Objective-C中,可以使用pthread_mutex来创建和管理互斥锁。
#import <pthread.h>
#import <Foundation/Foundation.h>

@interface MyClass : NSObject
{
    pthread_mutex_t mutex;
    int sharedValue;
}
- (void)updateSharedValue;
@end

@implementation MyClass

- (instancetype)init
{
    self = [super init];
    if (self) {
        sharedValue = 0;
        pthread_mutex_init(&mutex, NULL);
    }
    return self;
}

- (void)updateSharedValue
{
    pthread_mutex_lock(&mutex);
    sharedValue++;
    pthread_mutex_unlock(&mutex);
}

- (void)dealloc
{
    pthread_mutex_destroy(&mutex);
}
@end
  1. 信号量(Semaphore):信号量是一个计数器,它可以控制同时访问共享资源的线程数量。在Objective-C中,可以使用dispatch_semaphore来创建和管理信号量。
#import <dispatch/dispatch.h>
#import <Foundation/Foundation.h>

@interface MyClass : NSObject
{
    dispatch_semaphore_t semaphore;
    int sharedValue;
}
- (void)updateSharedValue;
@end

@implementation MyClass

- (instancetype)init
{
    self = [super init];
    if (self) {
        sharedValue = 0;
        semaphore = dispatch_semaphore_create(1);
    }
    return self;
}

- (void)updateSharedValue
{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    sharedValue++;
    dispatch_semaphore_signal(semaphore);
}

- (void)dealloc
{
    dispatch_release(semaphore);
}
@end
  1. 条件变量(Condition Variable):条件变量用于线程间的通信,当某个条件满足时,一个线程可以通知其他线程。在Objective-C中,可以使用pthread_cond来创建和管理条件变量。
#import <pthread.h>
#import <Foundation/Foundation.h>

@interface MyClass : NSObject
{
    pthread_mutex_t mutex;
    pthread_cond_t condition;
    int sharedValue;
    BOOL conditionMet;
}
- (void)waitForCondition;
- (void)signalCondition;
@end

@implementation MyClass

- (instancetype)init
{
    self = [super init];
    if (self) {
        sharedValue = 0;
        conditionMet = NO;
        pthread_mutex_init(&mutex, NULL);
        pthread_cond_init(&condition, NULL);
    }
    return self;
}

- (void)waitForCondition
{
    pthread_mutex_lock(&mutex);
    while (!conditionMet) {
        pthread_cond_wait(&condition, &mutex);
    }
    sharedValue++;
    pthread_mutex_unlock(&mutex);
}

- (void)signalCondition
{
    pthread_mutex_lock(&mutex);
    conditionMet = YES;
    pthread_cond_signal(&condition);
    pthread_mutex_unlock(&mutex);
}

- (void)dealloc
{
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&condition);
}
@end

性能优化技巧

减少锁的使用范围

锁的使用会带来一定的性能开销,因为它会限制并发执行。尽量缩小锁的保护范围,只在真正需要保护共享资源的代码块上加锁。

// 不好的示例,锁的范围过大
- (void)badExample
{
    pthread_mutex_lock(&mutex);
    // 大量不需要保护共享资源的代码
    for (int i = 0; i < 1000000; i++) {
        // 一些计算
    }
    sharedValue++;
    pthread_mutex_unlock(&mutex);
}

// 好的示例,缩小锁的范围
- (void)goodExample
{
    // 大量不需要保护共享资源的代码
    for (int i = 0; i < 1000000; i++) {
        // 一些计算
    }
    pthread_mutex_lock(&mutex);
    sharedValue++;
    pthread_mutex_unlock(&mutex);
}

选择合适的锁类型

不同的锁类型在不同的场景下有不同的性能表现。互斥锁适用于简单的资源保护,而读写锁(如pthread_rwlock)适用于读多写少的场景。

#import <pthread.h>
#import <Foundation/Foundation.h>

@interface MyClass : NSObject
{
    pthread_rwlock_t rwLock;
    int sharedValue;
}
- (void)readSharedValue;
- (void)writeSharedValue;
@end

@implementation MyClass

- (instancetype)init
{
    self = [super init];
    if (self) {
        sharedValue = 0;
        pthread_rwlock_init(&rwLock, NULL);
    }
    return self;
}

- (void)readSharedValue
{
    pthread_rwlock_rdlock(&rwLock);
    NSLog(@"Read value: %d", sharedValue);
    pthread_rwlock_unlock(&rwLock);
}

- (void)writeSharedValue
{
    pthread_rwlock_wrlock(&rwLock);
    sharedValue++;
    pthread_rwlock_unlock(&rwLock);
}

- (void)dealloc
{
    pthread_rwlock_destroy(&rwLock);
}
@end

使用线程局部存储(Thread - Local Storage)

线程局部存储允许每个线程拥有自己独立的变量副本,避免了共享资源的竞争。在Objective-C中,可以使用pthread_key_createpthread_setspecific来实现线程局部存储。

#import <pthread.h>
#import <Foundation/Foundation.h>

@interface MyClass : NSObject
{
    pthread_key_t threadKey;
}
- (void)setThreadLocalValue:(NSInteger)value;
- (NSInteger)getThreadLocalValue;
@end

@implementation MyClass

- (instancetype)init
{
    self = [super init];
    if (self) {
        pthread_key_create(&threadKey, NULL);
    }
    return self;
}

- (void)setThreadLocalValue:(NSInteger)value
{
    pthread_setspecific(threadKey, (void *)value);
}

- (NSInteger)getThreadLocalValue
{
    return (NSInteger)pthread_getspecific(threadKey);
}

- (void)dealloc
{
    pthread_key_delete(threadKey);
}
@end

避免不必要的线程创建和销毁

线程的创建和销毁是有一定开销的,尽量复用线程而不是频繁创建和销毁。可以使用线程池(如GCD的dispatch queue)来管理线程。

#import <dispatch/dispatch.h>
#import <Foundation/Foundation.h>

@interface MyClass : NSObject
{
    dispatch_queue_t customQueue;
}
- (void)performTask;
@end

@implementation MyClass

- (instancetype)init
{
    self = [super init];
    if (self) {
        customQueue = dispatch_queue_create("com.example.customQueue", DISPATCH_QUEUE_CONCURRENT);
    }
    return self;
}

- (void)performTask
{
    dispatch_async(customQueue, ^{
        // 执行任务
        NSLog(@"Task executed on custom queue");
    });
}

- (void)dealloc
{
    dispatch_release(customQueue);
}
@end

优化线程间通信

线程间通信也会影响性能,尽量减少线程间的同步通信,采用异步通信方式。例如,使用NSNotificationCenterGCD的信号量进行异步通知。

// 使用NSNotificationCenter进行异步通知
#import <Foundation/Foundation.h>

@interface MyClass : NSObject
{
    NSObject *observer;
}
- (void)registerForNotification;
- (void)sendNotification;
@end

@implementation MyClass

- (instancetype)init
{
    self = [super init];
    if (self) {
        [self registerForNotification];
    }
    return self;
}

- (void)registerForNotification
{
    observer = [[NSObject alloc] init];
    [[NSNotificationCenter defaultCenter] addObserver:observer selector:@selector(handleNotification:) name:@"MyNotification" object:nil];
}

- (void)sendNotification
{
    [[NSNotificationCenter defaultCenter] postNotificationName:@"MyNotification" object:nil];
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:observer];
    [observer release];
}
@end

@implementation NSObject
- (void)handleNotification:(NSNotification *)notification
{
    NSLog(@"Received notification");
}
@end

分析和调优

使用工具如 Instruments 来分析多线程应用程序的性能。Instruments 可以帮助我们找出性能瓶颈,如锁竞争、线程阻塞等。

  1. 使用 Instruments 的 Time Profiler:Time Profiler 可以记录应用程序中各个函数的执行时间,帮助我们找出耗时较长的函数。
  2. 使用 Instruments 的 Thread State Profiler:Thread State Profiler 可以分析线程的状态,如运行、阻塞等,帮助我们找出线程阻塞的原因。

多核处理器优化

现代设备大多配备多核处理器,充分利用多核处理器可以显著提高多线程应用程序的性能。

任务并行

将大任务分解为多个小任务,每个小任务在不同的线程或处理器核心上并行执行。例如,可以将图像渲染任务分解为多个区域的渲染任务,每个区域在不同线程上渲染。

#import <dispatch/dispatch.h>
#import <Foundation/Foundation.h>

// 假设这是一个图像渲染函数
void renderImageRegion(int region)
{
    // 模拟渲染操作
    for (int i = 0; i < 1000000; i++) {
        // 一些计算
    }
    NSLog(@"Rendered region %d", region);
}

@interface MyClass : NSObject
- (void)renderImage;
@end

@implementation MyClass

- (void)renderImage
{
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for (int i = 0; i < 4; i++) {
        dispatch_async(concurrentQueue, ^{
            renderImageRegion(i);
        });
    }
    dispatch_barrier_async(concurrentQueue, ^{
        NSLog(@"All regions rendered");
    });
}
@end

数据并行

对于需要处理大量数据的任务,可以将数据分成多个部分,每个部分在不同的线程或处理器核心上并行处理。例如,对一个大型数组进行排序,可以将数组分成多个子数组,每个子数组在不同线程上排序,最后合并结果。

#import <dispatch/dispatch.h>
#import <Foundation/Foundation.h>

// 简单的数组排序函数
void sortArray(int *array, int length)
{
    for (int i = 0; i < length - 1; i++) {
        for (int j = 0; j < length - i - 1; j++) {
            if (array[j] > array[j + 1]) {
                int temp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = temp;
            }
        }
    }
}

// 合并两个已排序的数组
void mergeArrays(int *array1, int length1, int *array2, int length2, int *result)
{
    int i = 0, j = 0, k = 0;
    while (i < length1 && j < length2) {
        if (array1[i] < array2[j]) {
            result[k++] = array1[i++];
        } else {
            result[k++] = array2[j++];
        }
    }
    while (i < length1) {
        result[k++] = array1[i++];
    }
    while (j < length2) {
        result[k++] = array2[j++];
    }
}

@interface MyClass : NSObject
- (void)sortLargeArray:(int *)array length:(int)length;
@end

@implementation MyClass

- (void)sortLargeArray:(int *)array length:(int)length
{
    int subArrayLength = length / 2;
    int *subArray1 = (int *)malloc(subArrayLength * sizeof(int));
    int *subArray2 = (int *)malloc((length - subArrayLength) * sizeof(int));
    memcpy(subArray1, array, subArrayLength * sizeof(int));
    memcpy(subArray2, array + subArrayLength, (length - subArrayLength) * sizeof(int));

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

    dispatch_group_async(group, concurrentQueue, ^{
        sortArray(subArray1, subArrayLength);
    });
    dispatch_group_async(group, concurrentQueue, ^{
        sortArray(subArray2, length - subArrayLength);
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        int *sortedArray = (int *)malloc(length * sizeof(int));
        mergeArrays(subArray1, subArrayLength, subArray2, length - subArrayLength, sortedArray);
        free(subArray1);
        free(subArray2);
        // 这里可以处理排序后的数组,如更新UI等
        free(sortedArray);
    });
    dispatch_release(group);
}
@end

常见问题及解决方法

死锁

死锁是多线程编程中常见的问题,当两个或多个线程相互等待对方释放资源时就会发生死锁。

  1. 死锁检测:Instruments 的 Deadlock Instrument 可以帮助检测死锁。当发生死锁时,它会显示死锁的线程和资源。
  2. 死锁预防:避免死锁的方法包括按顺序获取锁、避免嵌套锁、使用超时机制等。
// 按顺序获取锁,避免死锁
@interface DeadlockAvoidanceClass : NSObject
{
    pthread_mutex_t mutex1;
    pthread_mutex_t mutex2;
}
- (void)method1;
- (void)method2;
@end

@implementation DeadlockAvoidanceClass

- (instancetype)init
{
    self = [super init];
    if (self) {
        pthread_mutex_init(&mutex1, NULL);
        pthread_mutex_init(&mutex2, NULL);
    }
    return self;
}

- (void)method1
{
    pthread_mutex_lock(&mutex1);
    pthread_mutex_lock(&mutex2);
    // 执行任务
    pthread_mutex_unlock(&mutex2);
    pthread_mutex_unlock(&mutex1);
}

- (void)method2
{
    pthread_mutex_lock(&mutex1);
    pthread_mutex_lock(&mutex2);
    // 执行任务
    pthread_mutex_unlock(&mutex2);
    pthread_mutex_unlock(&mutex1);
}

- (void)dealloc
{
    pthread_mutex_destroy(&mutex1);
    pthread_mutex_destroy(&mutex2);
}
@end

资源竞争

资源竞争导致数据不一致,解决方法是使用同步机制,如前面提到的互斥锁、信号量等。同时,要确保同步机制的正确使用,避免锁争用过度影响性能。

线程安全的数据结构

在多线程环境中,使用线程安全的数据结构可以减少同步开销。例如,NSMutableArray不是线程安全的,而NSConcurrentMutableArray是线程安全的。

#import <Foundation/Foundation.h>

@interface ThreadSafeArrayExample : NSObject
{
    NSConcurrentMutableArray *concurrentArray;
}
- (void)addObject:(id)object;
- (id)objectAtIndex:(NSUInteger)index;
@end

@implementation ThreadSafeArrayExample

- (instancetype)init
{
    self = [super init];
    if (self) {
        concurrentArray = [[NSConcurrentMutableArray alloc] init];
    }
    return self;
}

- (void)addObject:(id)object
{
    [concurrentArray addObject:object];
}

- (id)objectAtIndex:(NSUInteger)index
{
    return [concurrentArray objectAtIndex:index];
}

- (void)dealloc
{
    [concurrentArray release];
}
@end

与其他技术结合优化

与Grand Central Dispatch(GCD)结合

GCD是一种基于队列的异步执行模型,它提供了一种简单而高效的方式来管理多线程编程。

  1. 使用GCD的队列:GCD提供了全局队列和自定义队列。全局队列适用于不需要特定优先级或顺序的任务,而自定义队列可以根据需求设置优先级和执行顺序。
#import <dispatch/dispatch.h>
#import <Foundation/Foundation.h>

@interface GCDExample : NSObject
- (void)performTasks;
@end

@implementation GCDExample

- (void)performTasks
{
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t customQueue = dispatch_queue_create("com.example.customQueue", DISPATCH_QUEUE_SERIAL);

    dispatch_async(globalQueue, ^{
        // 执行任务1
        NSLog(@"Task 1 executed on global queue");
    });

    dispatch_async(customQueue, ^{
        // 执行任务2
        NSLog(@"Task 2 executed on custom queue");
    });
}
@end
  1. 使用GCD的同步和异步函数dispatch_sync用于在指定队列上同步执行任务,dispatch_async用于异步执行任务。合理使用这两个函数可以控制任务的执行顺序和并发度。

与Operation Queue结合

NSOperationQueue是一个基于NSOperation的任务管理系统。NSOperation可以表示一个异步任务,NSOperationQueue可以管理多个NSOperation的执行。

  1. 创建和添加操作:可以创建自定义的NSOperation子类,并重写main方法来定义任务的执行逻辑。然后将操作添加到NSOperationQueue中执行。
#import <Foundation/Foundation.h>

@interface CustomOperation : NSOperation
@end

@implementation CustomOperation
- (void)main
{
    // 执行任务
    NSLog(@"Custom operation executed");
}
@end

@interface OperationQueueExample : NSObject
- (void)performTasks;
@end

@implementation OperationQueueExample

- (void)performTasks
{
    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    CustomOperation *operation1 = [[CustomOperation alloc] init];
    CustomOperation *operation2 = [[CustomOperation alloc] init];

    [operationQueue addOperation:operation1];
    [operationQueue addOperation:operation2];

    [operation1 release];
    [operation2 release];
    [operationQueue release];
}
@end
  1. 设置操作依赖NSOperation可以设置依赖关系,确保某些操作在其他操作完成后执行。
#import <Foundation/Foundation.h>

@interface CustomOperation : NSOperation
@end

@implementation CustomOperation
- (void)main
{
    // 执行任务
    NSLog(@"Custom operation executed");
}
@end

@interface OperationQueueExample : NSObject
- (void)performTasks;
@end

@implementation OperationQueueExample

- (void)performTasks
{
    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    CustomOperation *operation1 = [[CustomOperation alloc] init];
    CustomOperation *operation2 = [[CustomOperation alloc] init];
    CustomOperation *operation3 = [[CustomOperation alloc] init];

    [operation2 addDependency:operation1];
    [operation3 addDependency:operation2];

    [operationQueue addOperation:operation1];
    [operationQueue addOperation:operation2];
    [operationQueue addOperation:operation3];

    [operation1 release];
    [operation2 release];
    [operation3 release];
    [operationQueue release];
}
@end

通过合理运用这些性能优化技巧,结合多线程编程的基础原理和各种工具,在Objective - C多线程编程中能够显著提升应用程序的性能,为用户带来更流畅的体验。在实际开发中,需要根据具体的应用场景和需求,灵活选择和组合这些优化方法,不断调试和分析,以达到最佳的性能表现。同时,要密切关注新技术和工具的发展,及时更新知识,以适应不断变化的开发环境。