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 队列或手动锁实现。
  • 线程同步:多条线程按顺序执行任务,通过锁实现;锁定范围尽量小,同一把锁保护同一段代码。

posted @ 2015-08-17 20:54  Mr.陳  阅读(370)  评论(0)    收藏  举报