iOS开发基础91-线程同步技术与资源共享详解

在多线程编程中,尤其是在iOS开发中,经常需要处理多个线程访问共享资源的情况。多个线程同时访问同一块资源(如对象、变量、文件)可能会引发数据错乱和数据安全问题。因此,解决这些问题的关键是线程同步。线程同步的目标是确保多个线程按照一定的顺序对共享资源进行访问。

一、常见的线程同步技术

1. OSSpinLock

OSSpinLock被称为“自旋锁”,等待锁的线程会一直处于忙等(busy-wait)状态,占用CPU资源。自旋锁的一个缺点是可能会导致优先级反转问题:高优先级的线程因为占用CPU资源而无法释放锁,从而阻碍低优先级线程运行。由于这些问题,苹果已不再推荐使用OSSpinLock。使用自旋锁需要导入头文件:

#import <libkern/OSAtomic.h>

2. os_unfair_lock

os_unfair_lock用于替代OSSpinLock,从iOS 10开始支持。与OSSpinLock不同,os_unfair_lock使等待线程进入休眠状态,而不是忙等。这种机制大大提高了系统资源利用率和线程调度的灵活性。需要导入头文件:

#import <os/lock.h>

3. pthread_mutex

pthread_mutex,称为“互斥锁”,等待锁的线程会进入休眠状态。pthread_mutex有多种类型,包括普通锁、递归锁和条件锁。使用pthread_mutex需要导入头文件:

#import <pthread.h>

3.1 普通互斥锁

普通互斥锁的一个简单示例:

pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);

pthread_mutex_lock(&mutex);
// Critical section
pthread_mutex_unlock(&mutex);

pthread_mutex_destroy(&mutex);

3.2 递归互斥锁

递归互斥锁允许同一个线程多次加锁:

pthread_mutex_t recursiveMutex;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&recursiveMutex, &attr);
pthread_mutexattr_destroy(&attr);

pthread_mutex_lock(&recursiveMutex);
// Recursive lock operation
pthread_mutex_unlock(&recursiveMutex);

pthread_mutex_destroy(&recursiveMutex);

4. NSLock

NSLock是对pthread_mutex普通锁的封装,提供简化的API来进行锁操作:

NSLock *lock = [[NSLock alloc] init];
[lock lock];
// Critical section
[lock unlock];

5. NSRecursiveLock

NSRecursiveLock是对递归锁的封装,API与NSLock非常相似:

NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
[recursiveLock lock];
// Recursive lock operation
[recursiveLock unlock];

6. NSCondition

NSCondition是对互斥锁与条件变量的封装,适用于需要线程间通信的情况:

NSCondition *condition = [[NSCondition alloc] init];
[condition lock];
// Wait or signal condition
[condition unlock];

7. NSConditionLock

NSConditionLock进一步封装了NSCondition,可以设置具体的条件值:

NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:0];
[conditionLock lockWhenCondition:1];
// Critical section
[conditionLock unlockWithCondition:0];

8. Dispatch Semaphore

dispatch_semaphore(信号量)用于控制线程并发访问的最大数量。其初始值为1时,只允许一条线程访问资源,确保线程同步:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// Critical section
dispatch_semaphore_signal(semaphore);

9. Dispatch Queue

直接使用GCD的串行队列也可以实现线程同步:

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

dispatch_async(serialQueue, ^{
    // Critical section
});

10. @synchronized

@synchronized是对递归锁的封装,使用起来非常简便:

@synchronized(self) {
    // Critical section
}

二、iOS 线程同步方案性能比较

在选择适合的锁类型时,需要考虑性能与适用场景。以下为几种锁的性能比较:

  • os_unfair_lock: iOS 10 及以上版本支持,性能最高。
  • OSSpinLock: 不再推荐使用,存在优先级反转问题。
  • dispatch_semaphore: 性能较高,推荐使用。
  • pthread_mutex: 普通锁性能优良,适合跨平台使用。
  • dispatch_queue(DISPATCH_QUEUE_SERIAL): 性能稍差,适合Objective-C代码中使用。
  • NSLock: 相较于C语言互斥锁,性能略差。
  • NSCondition: 性能略低于NSLock。
  • pthread_mutex(recursive):递归锁性能稍逊于普通锁。
  • NSRecursiveLock: 递归锁性能略逊于普通锁。
  • NSConditionLock: 对条件值的支持,性能略差。
  • @synchronized: 递归锁封装,适合OC代码。

三、自旋锁与互斥锁选择

自旋锁的适用情况:

  • 预计线程等待锁的时间较短。
  • 临界区代码(加锁的代码)经常被调用,但竞争情况较少。
  • CPU资源不紧张,且是多核处理器。

互斥锁的适用情况:

  • 预计线程等待锁的时间较长。
  • 单核处理器。
  • 临界区中有IO操作。
  • 临界区代码较复杂或循环调用较多。
  • 临界区竞争非常激烈。

四、读写锁

场景:

  • 同一时间,只能有一个线程进行写操作。
  • 同一时间,允许有多个线程进行读操作。
  • 同一时间,不允许既有写操作又有读操作。

实现方案:

1. pthread_rwlock

读写锁适合文件或数据的多读单写操作,等待锁的线程处于休眠状态:

pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock, NULL);

pthread_rwlock_rdlock(&rwlock);
// Read operation
pthread_rwlock_unlock(&rwlock);

pthread_rwlock_wrlock(&rwlock);
// Write operation
pthread_rwlock_unlock(&rwlock);

pthread_rwlock_destroy(&rwlock);

2. dispatch_barrier_async

使用dispatch_barrier_async可以在并发队列中实现读写锁功能:

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

// Read operations
dispatch_async(concurrentQueue, ^{
    // Read operation
});

// Write operation
dispatch_barrier_async(concurrentQueue, ^{
    // Write operation
});

结论

线程同步技术在多线程编程中至关重要,使用合适的同步策略不仅能提高程序的稳定性,还能优化性能。本文详细介绍了常见的线程同步技术及其适用场景:

  • 自旋锁互斥锁的选择取决于等待时间、CPU资源和临界区代码的复杂性。
  • 读写锁适合多读单写的场景,如文件或数据的读写操作。

通过合理使用上述技术,开发者可以有效避免线程间的数据竞争,提高多线程程序的可靠性和性能。

posted @   Mr.陳  阅读(2403)  评论(1编辑  收藏  举报
编辑推荐:
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示