引言
关于并发编程,我们在前面的博客中已经介绍过了GCD和NSOperation&NSOperationQueue。这两种方案足以覆盖大多数开发场景。然而,理解NSThread仍然是有必要的。虽然它在现代开发中使用较少,但对于理解底层线程管理和并发编程的基础,NSThread仍然非常有帮助。
NSThread
什么是NSThread
NSThread是iOS和macOS平台上的一个基础类,用于直接管理线程。它提供了一种轻量级的方法来创建和管理应用程序中的多个执行现场,从而允许你在应用中并行处理多个任务。
NSThread创建与启动
NSThread允许我们手动创建和管理线程,通过它,我们可以在应用中执行并发任务。创建和启动一个NSThread有几种常见的方法:
使用init(target: Any, selector: Selector, object argument: Any?)方法创建线程
我们可以使用NSThread的init(target: Any, selector: Selector, object argument: Any?)方法来创建一个线程对象,然后手动启动它。
private func testNSThread() {
let thread = Thread(target: self, selector: #selector(threadEntryPoint(_:)), object: nil)
thread.start()
}
threadEntryPoint(_:)是一个将在新线程中执行的方法。调用thread.start()后,线程将开始执行,并运行threadEntryPoint(_:)方法中的代码。
使用类方法detachNewThreadSelector(_ selector: Selector, toTarget target: Any, with argument: Any?)创建并启动线程。
我们也可以使用NSThread提供了类方法detachNewThreadSelector(_ selector: Selector, toTarget target: Any, with argument: Any?)来创建和启动一个新的线程。
private func detachNewThread() {
Thread.detachNewThreadSelector(#selector(threadEntryPoint(_:)), toTarget: self, with: nil)
}
这种方式同样也适合在代码中快速启动一个新线程的场景。
使用performSelector(inBackground aSelector: Selector, with arg: Any?)创建后台线程
还可以直接调用NSObject的方法performSelector(inBackground aSelector: Selector, with arg: Any?)直接指定在后台线程执行的方法。
private func performSelector() {
performSelector(inBackground: #selector(threadEntryPoint(_:)), with: nil)
}
这种方式适合于需要快速启动后台任务的简单场景。
无论使用哪种方式创建和启动线程,我们都需要定义一个线程入口方法。这个方法将在线程启动后执行,通常用于处理后台任务。
@objc func threadEntryPoint(_ object: Any?) {
autoreleasepool {
// 处理线程任务
print("Thread is running")
// 模拟长时间任务
Thread.sleep(forTimeInterval: 2.0)
print("Thread is finishing")
}
}
注意在线程的入口方法中使用自动释放池来管理自动释放的对象,以避免内存泄漏。
手动管理线程的生命周期
在使用NSThread时,我们可以对线程的整个生命周期进行手动管理,包括线程的启动、暂停、恢复和取消。这种手动控制为某些特定应用场景提供了灵活性,但也增加了复杂性,因为我们需要考虑线程同步和线程安全问题。
启动线程
前面已经介绍了几种创建和启动线程的方案,我们使用第一种来举例,通过调用start()方法启动一个线程。线程启动后,它会立即进入可执行状态,并开始执行指定的入口方法。
let thread = Thread(target: self, selector: #selector(threadEntryPoint(_:)), object: nil)
thread.start()
暂停线程
NSThread并不提供直接暂停线程的方法,但是我们可以通过让线程进入睡眠状态来模拟暂停。这通常使用Thread.sleep(forTimeInterval:)或Thread.sleep(until:)方法。
Thread.sleep(forTimeInterval: 2.0)
上述代码会使现场暂停执行2秒钟,这种方法虽然简单,但是在实际开发中不建议频繁使用,因为它可能会导致线程的长时间阻塞。
恢复线程
恢复线程的执行通常是指从暂停状态中恢复执行。如果线程被阻塞或者是睡眠,只需阻塞条件接触或者睡眠时间结束,线程会自动恢复执行。
取消线程
NSThread支持线程的取消。我们可以通过调用线程的cancel()方法来标记线程为已取消状态。需要注意的是,cancel()方法并不会立即终止线程的执行,它只是设置了线程的取消标记,需要在线程的任务中定期检查isCancelled属性,并且在适当时机手动退出线程。
thread.cancel()
在线程的入口方法中,通过检查isCancelled来决定是否退出线程。
@objc func threadEntryPoint(_ object: Any?) {
autoreleasepool {
while !Thread.current.isCancelled {
// 执行任务
print("Thread is running")
// 模拟长时间任务
Thread.sleep(forTimeInterval: 1.0)
}
print("Thread is cancelled, exiting...")
}
}
通过这种方式,我们可以优雅地退出线程,避免资源泄漏或不完整的任务执行。
安全退出线程
确保线程安全退出时多线程编程中的一个关键问题。如果线程在执行中途被强制退出,可能会导致资源泄漏、数据一致等问题。因此,通常我们会在合适的地方检查isCancelled状态,并在需要时手动退出线程。
另一种退出线程的方式是调用exit()方法,这会立即终止当前线程的执行。不过,exit()方法的使用应该非常谨慎,因为它不考虑线程当前的状态,会强制终止线程。
Thread.exit()
结语
本篇博客主要介绍了多线程方案的NSThread,与GCD和NSOPerationqueue这样的高级抽象工具不同,NSThread允许开发者对线程的生命周期进行更细粒度的控制。我们可以手动创建、启动、暂停、恢复及终止线程,这在某些特定的情况下,提供了更大的灵活性。然后,这种灵活性也带来了更高的复杂性,因为我们需要自己处理线程的同步,锁定、以及其它多线程编程中常见问题。
虽然NSThread已不再是首选的并发处理方式,但在理解更高级的并发工具之前,掌握NSThread对理解并发编程的基本原理是非常有帮助的,希望本篇博客可以帮助你跟深刻的理解NSThread及并发编程。