Swift Thread 技术指南

211 阅读3分钟

引言

在多线程编程中,线程是并发执行任务的基本单元。Swift 提供了几种创建和管理线程的方式,其中之一是使用 NSThread。本文将详细探讨 NSThread 的使用、其内部工作原理以及相关的示例代码。

什么是 NSThread?

NSThread 是 Apple 提供的用于在 macOS 和 iOS 平台上创建和管理线程的一个类。虽然 Swift 引入了更现代的并发编程方式(如 GCD 和 OperationQueue),但了解 NSThread 仍然对理解低级线程管理和历史代码有帮助。

NSThread 的基本使用

NSThread 的基本用法非常简单,你可以通过实例化一个 NSThread 对象并调用 start() 方法来启动一个新线程。以下是一个简单的例子:

  • block形式

// 执行后台任务
func doSomeBackgroundTask() {
    Thread.sleep(forTimeInterval: 1.0)
    print(Thread.current)
}

// 创建线程
let thread1 = Thread.init {
    doSomeBackgroundTask()
}
// 启动线程
thread1.start()
  • Selector形式

/// 传递参数
class Client {
    @objc class UserInfo: NSObject {
        var name: String
        init(name: String) {
            self.name = name
        }
    }
    
    init() {
        let userInfo = UserInfo(name: "thread")
      	// 传递参数
        let thread = Thread(target: self, selector: #selector(doSomeBackgroundTask(userInfo:)), object: userInfo)
        thread.name = "selector thread"
        thread.start()
    }
    
    @objc func doSomeBackgroundTask(userInfo: UserInfo) {
        print(userInfo.name)
        Thread.sleep(forTimeInterval: 1.0)
        print(Thread.current)
    }
}
  • 自定义Thread

//自定义Thread
class MyThread: Thread {
    // 重写main()函数,执行后台任务
    override func main() {
        doSomeBackgroundTask()
    }
    private func doSomeBackgroundTask() {
        Thread.sleep(forTimeInterval: 1.0)
        print(Thread.current)
    }
}
let thread = MyThread()
thread.start()

线程保活

线程保活是指维持线程的存活状态,使其在需要时可以重新执行任务。保持线程在某些需要长时间运行任务的场景中非常有用。

  • 通过Runloop保持线程存活

// 注意:aliveThread 应该是一个全局变量
// var aliveThread: AliveThread!
// aliveThread = AliveThread()
// aliveThread.start()
class AliveThread: Thread {
    override func main() {
        let runloop = RunLoop.current
        runloop.add(NSMachPort(), forMode: .default)
        runloop.run()
    }
    
    @objc func action() {
        // 模拟耗时任务
        Thread.sleep(forTimeInterval: 1.0)
        print("receice, do work\(Thread.current)")
    }
    
    // 调用perfrom(), 可以在子线程中执行action()中的任务
    func perform() {
        perform(#selector(action), on: self, with: nil, waitUntilDone: true)
    }
}
  • 通过 While 循环

class FileMonitorThread: NSObject {
    private var thread: Thread!
    private var isRunning = true
    private var filePath: String

    init(filePath: String) {
        self.filePath = filePath
        super.init()
        thread = Thread(target: self, selector: #selector(threadEntryPoint), object: nil)
        thread.start()
    }
    
    // 通过while循环线程保活,每隔两秒检查文件是否存在
    @objc private func threadEntryPoint() {
        while isRunning {
            monitorFile()
            Thread.sleep(forTimeInterval: 2.0) // 每 2 秒检查一次
        }
    }
    
    private func monitorFile() {
        // 假设我们检查文件是否存在并打印结果
        let fileExists = FileManager.default.fileExists(atPath: filePath)
        if fileExists {
            print("File exists at path: \(filePath)")
        } else {
            print("File does not exist at path: \(filePath)")
        }
    }
    
    func stopThread() {
        isRunning = false
    }
}

实战: 生产-消费者

  • Thread 实现

class ProducerConsumer {
    private var buffer: [Int] = []
    private let condition = NSCondition()
    private let bufferSize = 10
    
    func produce() {
        for i in 0..<20 {
            condition.lock()
            while buffer.count == bufferSize {
                condition.wait()
            }
            buffer.append(i)
            print("Produced:\(i)")
            condition.signal()
            condition.unlock()
            Thread.sleep(forTimeInterval: 0.1)
        }
    }
    
    func consume() {
        for _ in 0..<20 {
            condition.lock()
            while buffer.isEmpty {
                condition.wait()
            }
            let item = buffer.removeFirst()
            print("Consumed:\(item)")
            condition.signal()
            condition.unlock()
            Thread.sleep(forTimeInterval: 0.1)
        }
    }
}
  • await/async 实现

 //await/async
actor ProducerConsumer {
    private var buffer: [Int] = []
    private let bufferSize = 10
    
    
    func produce() async {
        for i in 0..<20 {
            while buffer.count == bufferSize {
                // 让出线程
                await Task.yield()
            }
            
            buffer.append(i)
            print("Produced: \(i)")
            try? await Task.sleep(nanoseconds: UInt64(0.1 * Double(NSEC_PER_SEC)))
        }
    }
    
    func consume() async {
        for _ in 0..<20 {
            while buffer.isEmpty {
                // 让出线程
                await Task.yield()
            }
            let item = buffer.removeFirst()
            print("Consumed: \(item)")
            try? await Task.sleep(nanoseconds: UInt64(0.1 * Double(NSEC_PER_SEC)))
        }
    }
}

结论

自己创建线程使得用户可以更精细的操控线程,但是他们会增加代码的不确定性。线程是一种相对底层且复杂的并发模型,掌握线程知识可以为GCD,Operation Queue,await/asyc 打下坚实基础。

参考

Thread Programming Guide