iOS开发基础35-多线程之NSThread
NSThread 多线程深度解析:线程创建、状态管理、安全隐患与原子属性
本文系统梳理 NSThread 多线程编程:线程创建与启动、线程状态生命周期(新建/就绪/运行/阻塞/死亡)、多线程安全隐患与互斥锁/自旋锁、atomic 与 nonatomic 属性选择,以及底层逻辑。
一、NSThread 基本使用
1. 创建与启动
一个 NSThread 对象代表一条线程:
// 创建线程(不自动启动)
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
thread.name = @"com.example.worker"; // 设置线程名(便于调试)
[thread start]; // 启动线程,进入就绪→运行状态,在线程中执行 self 的 run 方法
- (void)run {
NSLog(@"线程任务: %@", [NSThread currentThread]);
}
2. 主线程相关
// 获取主线程
NSThread *main = [NSThread mainThread];
// 判断当前线程是否为主线程(实例方法)
BOOL isMain = [[NSThread currentThread] isMainThread];
// 判断当前线程是否为主线程(类方法,更常用)
BOOL isMain2 = [NSThread isMainThread];
3. 当前线程与线程名
// 获取当前线程
NSThread *current = [NSThread currentThread];
// 设置/获取线程名
current.name = @"worker-thread";
NSString *name = current.name;
4. 其他创建方式
// 方式一:detach(创建后自动启动,无法获取线程对象)
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
// 方式二:隐式创建并启动(NSObject 分类方法)
[self performSelectorInBackground:@selector(run) withObject:nil];
| 创建方式 | 是否自动启动 | 能否获取线程对象 | 能否详细配置 |
|---|---|---|---|
initWithTarget: + start |
否 | 能 | 能 |
detachNewThreadSelector: |
是 | 否 | 否 |
performSelectorInBackground: |
是 | 否 | 否 |
5. 线程详细配置
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
// 线程优先级(0.0 ~ 1.0,默认 0.5,已废弃,推荐 qualityOfService)
thread.threadPriority = 0.8;
// 服务质量(iOS 8+,替代 threadPriority)
thread.qualityOfService = NSQualityOfServiceUserInitiated;
// 栈大小(必须是 4KB 的倍数,子线程默认 512KB,主线程 1MB)
thread.stackSize = 1024 * 1024; // 1MB
[thread start];
qualityOfService枚举:NSQualityOfServiceUserInteractive(用户交互,最高)、UserInitiated(用户发起)、Utility(实用)、Background(后台,最低)、Default(默认)。
二、线程状态与生命周期
1. 线程状态
新建 (New)
↓ start
就绪 (Ready) ←──────┐
↓ CPU 调度 │
运行 (Running) │
↓ sleep/等待锁 │
阻塞 (Blocked) ──────┘
↓ 任务完成 / exit
死亡 (Dead)
| 状态 | 说明 |
|---|---|
| 新建(New) | alloc/init 创建线程对象,尚未启动 |
| 就绪(Ready) | 调用 start 后,等待 CPU 调度 |
| 运行(Running) | CPU 调度到该线程,正在执行任务 |
| 阻塞(Blocked) | sleep、等待锁、等待资源,暂停执行 |
| 死亡(Dead) | 任务执行完毕或调用 exit,线程销毁 |
2. 控制线程状态
// 启动:新建 → 就绪 → 运行
[thread start];
// 阻塞(暂停)当前线程
[NSThread sleepForTimeInterval:2.0]; // 休眠 2 秒
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]]; // 休眠到指定时间
// 强制停止当前线程(进入死亡,谨慎使用,不会执行清理代码)
[NSThread exit];
3. 取消线程
// 标记线程为取消状态(不立即停止,需在任务中检查 isCancelled)
[thread cancel];
- (void)run {
while (!self.isCancelled) { // 检查取消标记
// 执行任务
if (self.isCancelled) break;
}
NSLog(@"线程结束");
}
cancel只是设置isCancelled=YES标记,不会强制终止线程,需在任务循环中主动检查。exit会立即终止线程,不执行后续清理代码,可能导致资源泄漏,应尽量避免使用。
4. 线程状态属性
BOOL executing = thread.isExecuting; // 是否正在执行
BOOL finished = thread.isFinished; // 是否完成(死亡)
BOOL cancelled = thread.isCancelled; // 是否被标记取消
线程一旦死亡(
isFinished=YES),无法再次启动,需重新创建线程对象。
三、多线程安全隐患与解决方案
1. 安全隐患
多个线程同时访问同一块共享资源(对象、变量、文件)时,可能引发数据错乱和崩溃。典型场景:多线程同时对 NSMutableArray 执行 addObject:、多线程同时修改同一属性。
2. 互斥锁 @synchronized
- (void)addMoney {
@synchronized(self) { // 加锁(self 为锁对象)
// 操作共享资源
self.totalMoney += 100;
} // 自动解锁
}
使用注意:
- 锁定同一段代码必须使用同一把锁(同一个锁对象),多把锁无效。
- 锁定范围尽量小,避免降低并发效率。
- 只有一处加锁时,通常使用
self作为锁对象。 @synchronized内部使用递归锁(同一线程可多次加锁不会死锁),但性能较低。
线程同步:多条线程按顺序执行任务(通过锁实现),称为线程同步。互斥锁是线程同步的一种实现。
3. 其他常用锁
| 锁 | 说明 | 适用场景 |
|---|---|---|
@synchronized |
递归锁,Objective-C 语法级 | 简单场景,性能要求不高 |
NSLock |
互斥锁,面向对象 | 一般互斥场景 |
NSRecursiveLock |
递归锁,面向对象 | 同一线程可能多次加锁 |
dispatch_semaphore |
信号量,GCD | 高性能,可控制并发数 |
os_unfair_lock |
不公平锁,iOS 10+ | 高性能短任务(替代 OSSpinLock) |
// NSLock
@property (nonatomic, strong) NSLock *lock;
[self.lock lock];
// 操作共享资源
[self.lock unlock];
// dispatch_semaphore(信号量,value=1 相当于互斥锁)
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
// 操作共享资源
dispatch_semaphore_signal(sem);
// os_unfair_lock(iOS 10+)
#import <os/lock.h>
os_unfair_lock unfairLock = OS_UNFAIR_LOCK_INIT;
os_unfair_lock_lock(&unfairLock);
// 操作共享资源
os_unfair_lock_unlock(&unfairLock);
4. 自旋锁与互斥锁
| 对比项 | 互斥锁 | 自旋锁 |
|---|---|---|
| 等待方式 | 线程休眠,等待锁释放后被唤醒 | 线程忙等(循环检查),不休眠 |
| 适用场景 | 锁持有时间较长 | 锁持有时间极短 |
| 上下文切换 | 有(休眠/唤醒) | 无(忙等) |
| 性能 | 长任务更优 | 短任务更优 |
OSSpinLock已在 iOS 10 中废弃,存在优先级反转问题(低优先级线程持有锁时,高优先级线程忙等占用 CPU,导致低优先级线程无法释放锁)。应使用os_unfair_lock替代。
5. 无锁方案:GCD 串行队列/栅栏
除了加锁,还可通过 GCD 串行队列或栅栏函数实现线程安全,避免手动锁:
// 方案一:串行队列(所有任务顺序执行,天然线程安全)
dispatch_queue_t safeQueue = dispatch_queue_create("com.example.safe", DISPATCH_QUEUE_SERIAL);
dispatch_async(safeQueue, ^{
self.totalMoney += 100; // 写操作
});
// 方案二:并发队列 + 栅栏(多读单写)
dispatch_queue_t queue = dispatch_queue_create("com.example.rw", DISPATCH_QUEUE_CONCURRENT);
// 读操作(并发)
dispatch_async(queue, ^{ NSLog(@"读: %ld", self.totalMoney); });
// 写操作(栅栏,独占)
dispatch_barrier_async(queue, ^{
self.totalMoney += 100;
});
四、原子属性与非原子属性
1. 属性修饰符
定义属性时,atomic 和 nonatomic 控制 getter/setter 是否加锁:
| 修饰符 | 是否加锁 | 默认值 | 说明 |
|---|---|---|---|
atomic |
是 | 默认 | getter/setter 原子性,多线程安全(仅属性访问) |
nonatomic |
否 | 非默认 | 不加锁,性能高,非线程安全 |
// atomic(默认,可省略)
@property (atomic, strong) NSString *name;
// nonatomic(iOS 开发推荐)
@property (nonatomic, strong) NSString *name;
atomic不只是 setter 加锁,getter 也加锁,保证 getter/setter 操作的原子性。在 MRC 下,atomic 还保证 retain/release 的安全处理。
2. atomic 的局限性
atomic 只保证属性的 getter/setter 单次访问的原子性,不保证整体操作的线程安全:
// 即使 name 是 atomic,以下操作仍非线程安全
self.name = @"a"; // setter 原子
NSString *current = self.name; // getter 原子
self.name = [current stringByAppendingString:@"b"]; // 读-改-写 非原子,多线程下可能丢失数据
对于 NSMutableArray、NSMutableDictionary 等容器,addObject:/removeObject: 等操作不受 atomic 保护,仍需手动加锁或使用串行队列。
3. 选择建议
- iOS 开发中所有属性声明为
nonatomic,因为移动设备内存资源有限,atomic 加锁性能开销较大。 - 避免多线程争抢同一资源,将共享资源访问限制在同一线程(如主线程)或串行队列。
- 如需线程安全,优先使用 GCD 串行队列/栅栏函数,而非依赖 atomic 属性。
- 复杂的数据竞争问题交由服务器处理,减轻客户端压力。
五、Swift 版本对照
线程创建
// 创建并启动
let thread = Thread(target: self, selector: #selector(run), object: nil)
thread.name = "com.example.worker"
thread.qualityOfService = .userInitiated
thread.stackSize = 1024 * 1024
thread.start()
// detach(自动启动)
Thread.detachNewThreadSelector(#selector(run), toTarget: self, with: nil)
// 隐式创建
performSelector(inBackground: #selector(run), with: nil)
@objc func run() {
print("线程: \(Thread.current)")
}
线程状态
Thread.sleep(forTimeInterval: 2.0) // 休眠
Thread.sleep(until: Date().addingTimeInterval(2))
thread.cancel() // 标记取消
thread.isExecuting // 是否执行中
thread.isFinished // 是否完成
thread.isCancelled // 是否取消
Thread.exit() // 强制退出(谨慎)
线程同步
// NSLock
let lock = NSLock()
lock.lock()
// 操作
lock.unlock()
// NSRecursiveLock(递归锁)
let recursiveLock = NSRecursiveLock()
// DispatchSemaphore(信号量)
let sem = DispatchSemaphore(value: 1)
sem.wait()
// 操作
sem.signal()
// os_unfair_lock
import os
var unfairLock = os_unfair_lock()
os_unfair_lock_lock(&unfairLock)
// 操作
os_unfair_lock_unlock(&unfairLock)
// GCD 串行队列(线程安全)
let safeQueue = DispatchQueue(label: "com.example.safe")
safeQueue.async {
// 操作共享资源
}
Swift 中没有
@synchronized,可使用objc_sync_enter/objc_sync_exit或上述锁替代。
属性
// Swift 中属性默认非原子(类似 nonatomic)
var name: String = ""
// 如需原子性,需手动加锁实现
private let lock = NSLock()
private var _name: String = ""
var name: String {
get { lock.lock(); defer { lock.unlock() }; return _name }
set { lock.lock(); defer { lock.unlock() }; _name = newValue }
}
六、总结
- NSThread 创建:
initWithTarget:+start(可配置)、detachNewThreadSelector:(自动启动)、performSelectorInBackground:(隐式创建);可配置 name、qualityOfService、stackSize。 - 线程状态:新建→就绪→运行→阻塞→死亡;
start启动,sleepForTimeInterval:阻塞,exit强制死亡,cancel标记取消(需检查 isCancelled);死亡后无法重启。 - 安全隐患:多线程共享资源导致数据错乱;通过互斥锁
@synchronized、NSLock、dispatch_semaphore、os_unfair_lock 保证线程安全;也可使用 GCD 串行队列/栅栏函数无锁实现。 - 自旋锁 vs 互斥锁:互斥锁休眠等待(长任务优),自旋锁忙等(短任务优);OSSpinLock 因优先级反转已废弃,使用 os_unfair_lock 替代。
- atomic/nonatomic:atomic 为 getter/setter 加锁(默认),nonatomic 不加锁(iOS 推荐);atomic 只保证单次属性访问原子性,不保证读-改-写和容器操作的线程安全;iOS 开发全部属性用 nonatomic,线程安全通过 GCD 队列或手动锁实现。
- 线程同步:多条线程按顺序执行任务,通过锁实现;锁定范围尽量小,同一把锁保护同一段代码。

浙公网安备 33010602011771号