Swift | GCD Review

1. 并發隊列 + 同步任務

注意是主線程執行,要避免UI堵塞問題

let queue = DispatchQueue(label: "syncConcurrent", attributes: .concurrent)
        queue.sync {
            for i in 0...2 {
                Thread.sleep(forTimeInterval: 0.5)
                print("Sync 1 \(i) [\(Thread.current)]")
            }
        }
        queue.sync {
            for i in 0...2 {
                Thread.sleep(forTimeInterval: 0.5)
                print("Sync 2 \(i) [\(Thread.current)]")
            }
        }
        queue.sync {
            for i in 0...2 {
                Thread.sleep(forTimeInterval: 0.5)
                print("Sync 3 \(i) [\(Thread.current)]")
            }
        }
        print("All done")

輸出:只有一條線程(主線程),所有任務依次有序執行

Sync 1 0 [<_NSMainThread: 0x600001d18380>{number = 1, name = main}]
Sync 1 1 [<_NSMainThread: 0x600001d18380>{number = 1, name = main}]
Sync 1 2 [<_NSMainThread: 0x600001d18380>{number = 1, name = main}]
Sync 2 0 [<_NSMainThread: 0x600001d18380>{number = 1, name = main}]
Sync 2 1 [<_NSMainThread: 0x600001d18380>{number = 1, name = main}]
Sync 2 2 [<_NSMainThread: 0x600001d18380>{number = 1, name = main}]
Sync 3 0 [<_NSMainThread: 0x600001d18380>{number = 1, name = main}]
Sync 3 1 [<_NSMainThread: 0x600001d18380>{number = 1, name = main}]
Sync 3 2 [<_NSMainThread: 0x600001d18380>{number = 1, name = main}]
All done

2. 并發隊列 + 異步執行

let queue = DispatchQueue(label: "asyncConcurrent", attributes: .concurrent)
        queue.async {
            for i in 0...2 {
                Thread.sleep(forTimeInterval: 0.5)
                print("Async 1 \(i) [\(Thread.current)]")
            }
        }
        queue.async {
            for i in 0...2 {
                Thread.sleep(forTimeInterval: 0.5)
                print("Async 2 \(i) [\(Thread.current)]")
            }
        }
        queue.async {
            for i in 0...2 {
                Thread.sleep(forTimeInterval: 0.5)
                print("Async 3 \(i) [\(Thread.current)]")
            }
        }
        print("All done")

輸出:多(3)條線程,線程間(無序)交替執行加入隊列中的任務(有序)

All done
Async 3 0 [<NSThread: 0x600002b7e000>{number = 6, name = (null)}]
Async 2 0 [<NSThread: 0x600002b702c0>{number = 3, name = (null)}]
Async 1 0 [<NSThread: 0x600002b7e4c0>{number = 7, name = (null)}]
Async 2 1 [<NSThread: 0x600002b702c0>{number = 3, name = (null)}]
Async 3 1 [<NSThread: 0x600002b7e000>{number = 6, name = (null)}]
Async 1 1 [<NSThread: 0x600002b7e4c0>{number = 7, name = (null)}]
Async 2 2 [<NSThread: 0x600002b702c0>{number = 3, name = (null)}]
Async 1 2 [<NSThread: 0x600002b7e4c0>{number = 7, name = (null)}]
Async 3 2 [<NSThread: 0x600002b7e000>{number = 6, name = (null)}]

3. 串行隊列 + 同步任務

let queue = DispatchQueue(label: "syncSerial")
        queue.sync {
            for i in 0...2 {
                Thread.sleep(forTimeInterval: 0.5)
                print("Sync Serial: 1 \(i) [\(Thread.current)]")
            }
        }
        queue.sync {
            for i in 0...2 {
                Thread.sleep(forTimeInterval: 0.5)
                print("Sync Serial: 2 \(i) [\(Thread.current)]")
            }
        }
        queue.sync {
            for i in 0...2 {
                Thread.sleep(forTimeInterval: 0.5)
                print("Sync Serial: 3 \(i) [\(Thread.current)]")
            }
        }
        print("All done")

輸出:主線程+任務有序

Sync Serial: 1 0 [<_NSMainThread: 0x6000005f4000>{number = 1, name = main}]
Sync Serial: 1 1 [<_NSMainThread: 0x6000005f4000>{number = 1, name = main}]
Sync Serial: 1 2 [<_NSMainThread: 0x6000005f4000>{number = 1, name = main}]
Sync Serial: 2 0 [<_NSMainThread: 0x6000005f4000>{number = 1, name = main}]
Sync Serial: 2 1 [<_NSMainThread: 0x6000005f4000>{number = 1, name = main}]
Sync Serial: 2 2 [<_NSMainThread: 0x6000005f4000>{number = 1, name = main}]
Sync Serial: 3 0 [<_NSMainThread: 0x6000005f4000>{number = 1, name = main}]
Sync Serial: 3 1 [<_NSMainThread: 0x6000005f4000>{number = 1, name = main}]
Sync Serial: 3 2 [<_NSMainThread: 0x6000005f4000>{number = 1, name = main}]
All done

4. 串行隊列 + 異步任務

let queue = DispatchQueue(label: "asyncSerial")
        queue.async {
            for i in 0...2 {
                Thread.sleep(forTimeInterval: 0.5)
                print("Async Serial: 1 \(i) [\(Thread.current)]")
            }
        }
        queue.async {
            for i in 0...2 {
                Thread.sleep(forTimeInterval: 0.5)
                print("Async Serial: 2 \(i) [\(Thread.current)]")
            }
        }
        queue.async {
            for i in 0...2 {
                Thread.sleep(forTimeInterval: 0.5)
                print("Async Serial: 3 \(i) [\(Thread.current)]")
            }
        }
        print("All done")

輸出: 一條線程(非主線程)+任務有序

Async Serial: 1 0 [<NSThread: 0x600000869a00>{number = 6, name = (null)}]
Async Serial: 1 1 [<NSThread: 0x600000869a00>{number = 6, name = (null)}]
Async Serial: 1 2 [<NSThread: 0x600000869a00>{number = 6, name = (null)}]
Async Serial: 2 0 [<NSThread: 0x600000869a00>{number = 6, name = (null)}]
Async Serial: 2 1 [<NSThread: 0x600000869a00>{number = 6, name = (null)}]
Async Serial: 2 2 [<NSThread: 0x600000869a00>{number = 6, name = (null)}]
Async Serial: 3 0 [<NSThread: 0x600000869a00>{number = 6, name = (null)}]
Async Serial: 3 1 [<NSThread: 0x600000869a00>{number = 6, name = (null)}]
Async Serial: 3 2 [<NSThread: 0x600000869a00>{number = 6, name = (null)}]

5. GCD Group

func request(task: Int, time: TimeInterval, completion: @escaping ()->Void) {
        DispatchQueue.global(qos: .background).async {
            print("Requesting task: \(task) ... started")
            Thread.sleep(forTimeInterval: time)
            print("Requesting task: \(task) ... finished")
            DispatchQueue.main.async(execute: completion)
        }
    }

let group = DispatchGroup()
        for i in 1...3 {
            group.enter()
            request(task: i, time: Double(3-i)) {
                group.leave()
            }
        }
        group.notify(queue: .main) { [weak self] in
            print("All requests are finished!")
            self?.view.backgroundColor = .systemGreen
        }

輸出:

Requesting task: 1 ... started
Requesting task: 3 ... started
Requesting task: 2 ... started
Requesting task: 3 ... finished
Requesting task: 2 ... finished
Requesting task: 1 ... finished
All requests are finished!

參考:https://blog.csdn.net/Hello_Hwc/article/details/54293280

wait()阻塞group線程

for i in 1...3 {
/// 等待一秒再執行下一個任務
            let _ = group.wait(timeout: .now() + .seconds(1))
            
            group.enter()
            request(task: i, time: Double(3-i)) {
                group.leave()
            }
        }

輸出:

Requesting task: 1 ... started
Requesting task: 2 ... started
Requesting task: 3 ... started
Requesting task: 3 ... finished
Requesting task: 2 ... finished
Requesting task: 1 ... finished
All requests are finished!

串行隊列執行異步group任務

let group = DispatchGroup()
        let queue = DispatchQueue(label: "requestTaskSerial", qos: .background)
        for i in 1...3 {
            group.enter()
            queue.async(group: group){ [weak self] in
                self?.request(task: i, time: 1) {
                    group.leave()
                }
            }
        }
        group.notify(queue: .main) { [weak self] in
            print("All requests are finished!")
            self?.view.backgroundColor = .systemGreen
        }

輸出:

Requesting task: 1 ... started
Requesting task: 2 ... started
Requesting task: 3 ... started
Requesting task: 1 ... finished
Requesting task: 2 ... finished
Requesting task: 3 ... finished
All requests are finished!

Semaphore

DispatchSemaphore provides an efficient implementation of a traditional counting semaphore, which can be used to control access to a resource across multiple execution contexts.
DispatchSemaphore是傳統計數信號量的封裝,用來控制資源被多任務訪問的情況。

let group = DispatchGroup()
        let queue = DispatchQueue(label: "requestTaskSerial", qos: .background, attributes: .concurrent)
/*
        // 創建一個新的信號量,參數value代表信號量資源池的初始數量。
        //    value < 0, 返回NULL
        //    value = 0, 多線程在等待某個特定線程的結束。
        //    value > 0, 資源數量,可以由多個線程使用。
*/
        let semaphore = DispatchSemaphore(value: 2)
        for i in 1...3 {
            group.enter()
            queue.async(group: group){ [weak self] in
                semaphore.wait()
                self?.request(task: i, time: Double(3-i)) {
                    group.leave()
                    semaphore.signal()
                }
            }
        }
        group.notify(queue: .main) { [weak self] in
            print("All requests are finished!")
            self?.view.backgroundColor = .systemGreen
        }

輸出:同時最多只能有2個異步任務在執行

Requesting task: 3 ... started
Requesting task: 3 ... finished
Requesting task: 1 ... started
Requesting task: 2 ... started
Requesting task: 2 ... finished
Requesting task: 1 ... finished
All requests are finished!

Barrier

barrier翻譯過來就是屏障。在一個并行queue里,很多時候,我們提交一個新的任務需要這樣做。

  • queue里已有任務執行完了新任務才開始
  • 新任務開始后提交的任務都要等待新任務執行完畢才能繼續執行

特別注意:

以barrier flag提交的任務能夠保證其在并行隊列執行的時候,是唯一的一個任務。(只對自己創建的隊列有效,對gloablQueue無效)

let group = DispatchGroup()
        let queue = DispatchQueue(label: "requestTaskSerial", qos: .background, attributes: .concurrent)
        
        for i in 1...3 {
            group.enter()
            queue.async(flags: .barrier){
                print("Requesting task: \(i) ... started")
                Thread.sleep(forTimeInterval: Double(3-i))
                print("Requesting task: \(i) ... finished")
                group.leave()
            }
        }
        group.notify(queue: .main) { [weak self] in
            print("All requests are finished!")
            self?.view.backgroundColor = .systemGreen
        }

輸出:類似同步任務順序執行

Requesting task: 1 ... started
Requesting task: 1 ... finished
Requesting task: 2 ... started
Requesting task: 2 ... finished
Requesting task: 3 ... started
Requesting task: 3 ... finished
All requests are finished!

let queue = DispatchQueue(label: "requestTaskSerial", qos: .background, attributes: .concurrent)
        
        for i in 1...3 {
            group.enter()
            queue.async(flags: .barrier){ [weak self] in
                self?.request(task: i, time: Double(3-i), completion: {
                    group.leave()
                })
            }
        }
        group.notify(queue: .main) { [weak self] in
            print("All requests are finished!")
            self?.view.backgroundColor = .systemGreen
        }

輸出:異步任務順序執行

Requesting task: 3 ... started
Requesting task: 3 ... finished
Requesting task: 2 ... started
Requesting task: 1 ... started
Requesting task: 2 ... finished
Requesting task: 1 ... finished
All requests are finished!

Operation & OperationQueue

Operation

自定義同步operation只需要重寫main()方法,然后當main方法體執行完后,就isFinished,一般用于同步操作(如數據讀取)

自定義異步operation時,需要手動維護isFinishedisExecuting等狀態,所以較為復雜

class RequestOperation: Operation {
    var task: Int
    var time: TimeInterval
    
    init (task: Int, time: TimeInterval, completion: @escaping ()->Void) {
        self.task = task
        self.time = time
        
        super.init()
        self.completionBlock = completion
    }
    
    private var _finished: Bool = false {
        willSet{
            willChangeValue(forKey: "isFinished")
        }
        didSet{
            didChangeValue(forKey: "isFinished")
        }
    }
    private var _executing: Bool = false {
        willSet{
            willChangeValue(forKey: "isExecuting")
        }
        didSet{
            didChangeValue(forKey: "isExecuting")
        }
    }
    
    override var isFinished: Bool {
        return _finished
    }
    override var isExecuting: Bool {
        return _executing
    }
    override var isAsynchronous: Bool {
        return true
    }
    
    private func done() {
        super.cancel()
        
        _executing = false
        _finished = true
        completionBlock?()
    }
    
// 注意加鎖,避免多次調用
    override func cancel() {
        objc_sync_enter(self)
        done()
        objc_sync_exit(self)
    }
    
    override func start() {
        guard !isCancelled else {
            done()
            return
        }
        _executing = true
        request(task: task, time: time) { [weak self] in
            self?.done()
        }
    }
}

使用:

func request(task: Int, time: TimeInterval, completion: (()->Void)?) {
    DispatchQueue.global().async {
        print("Requesting task: \(task) ... started")
        Thread.sleep(forTimeInterval: time)
        print("Requesting task: \(task) ... finished")
        DispatchQueue.main.async(execute: completion ?? {})
    }
}

@IBAction func startOneByOneRequesting(_ sender: UIControl) {
        let queue = OperationQueue()
        
        var previousOperation: Operation?
        for i in 1...3 {
            let op = RequestOperation(task: i, time: Double(3-i), completion: {
                print("Operation \(i) is completed!")
                if i == 3 {
                    print("All tasks are done !!!")
                }
            })
            if let previous = previousOperation {
                op.addDependency(previous)
            }
            queue.addOperation(op)
            previousOperation = op
        }
    }

輸出:

Requesting task: 1 ... started
Requesting task: 1 ... finished
Operation 1 is completed!
Requesting task: 2 ... started
Requesting task: 2 ... finished
Operation 2 is completed!
Requesting task: 3 ... started
Requesting task: 3 ... finished
Operation 3 is completed!
All tasks are done !!!

OperationQueue

  • 最大并發操作數:maxConcurrentOperationCount

    • maxConcurrentOperationCount 默認情況下為-1,表示不進行限制,可進行并發執行。
    • maxConcurrentOperationCount 為1時,隊列為串行隊列。只能串行執行。
  • queuePriority 屬性決定了進入準備就緒狀態下的操作之間的開始執行順序。并且,優先級不能取代依賴關系。

  • 線程間的通信

在 iOS 開發過程中,我們一般在主線程里邊進行 UI 刷新,例如:點擊、滾動、拖拽等事件。我們通常把一些耗時的操作放在其他線程,比如說圖片下載、文件上傳等耗時操作。而當我們有時候在其他線程完成了耗時操作時,需要回到主線程,那么就用到了線程之間的通訊。

  • 線程同步:

    • 可理解為線程 A 和 線程 B 一塊配合,A 執行到一定程度時要依靠線程 B 的某個結果,于是停下來,示意 B 運行;B 依言執行,再將結果給 A;A 再繼續操作。
    • 若每個線程中對全局變量、靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執行寫操作(更改變量),一般都需要考慮線程同步,否則的話就可能影響線程安全。
  • 線程安全:

如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。

  • 線程安全解決方案:
    • 可以給線程加鎖,在一個線程執行該操作的時候,不允許其他線程進行操作。iOS 實現線程加鎖有很多種方式。@synchronized、 NSLock、NSRecursiveLock、NSCondition、NSConditionLock、pthread_mutex、dispatch_semaphore、OSSpinLock、atomic(property) set/ge等等各種方式。這里我們使用 NSLock 對象來解決線程同步問題。NSLock 對象可以通過進入鎖時調用 lock 方法,解鎖時調用 unlock 方法來保證線程安全。

var ticketSurplusCount = 50
    override func viewDidLoad() {
        super.viewDidLoad()
        
        ///1.1 創建代表北京火車票售賣窗口
        let operationForBeiJing = OperationQueue()
        operationForBeiJing.maxConcurrentOperationCount = 1;
        ///1.2 創建賣票操作 op1
        let op1 = BlockOperation{ 
            self.saleTicketSafe()
        }
        ///1.3 添加操作
        operationForBeiJing.addOperation(op1)
        
        ///2.1創建代表上海火車票售賣窗口
        let operationForShangHai = OperationQueue()
        operationForShangHai.maxConcurrentOperationCount = 1;
        ///2.2創建賣票操作 op2
        let op2 = BlockOperation{
            self.saleTicketSafe()
        }
        ///2.3 添加操作
        operationForShangHai.addOperation(op2)
    }

private func saleTicketSafe(){
        while true {
            objc_sync_enter(self)
            if self.ticketSurplusCount > 0 {
                self.ticketSurplusCount-=1;
                print("剩余票數:\(self.ticketSurplusCount) 窗口:\(Thread.current)")
                sleep(2)
            }
            objc_sync_exit(self)
            
            if self.ticketSurplusCount <= 0 {
                print("所有火車票均已售完")
                break
            }
        }
    }

迭代任務

如果一個任務可以分解為多個相似但獨立的子任務,那么迭代任務是提高性能最適合的選擇。

let queue = DispatchQueue.global() // 全局并發隊列
queue.async {
    DispatchQueue.concurrentPerform(iterations: 100) {(index) -> Void in
        // do something
    }
    //可以轉至主線程執行其他任務
    DispatchQueue.main.async {
        // do something
    }
}
  • 示例

    var divideds = [Int]()
    lazy var nums = Array(1...100000)
    
    @IBAction func syncConcurrent(_ sender: UIControl) {
        let queue = DispatchQueue.global(qos: .userInitiated)
        
        func isNumber(_ value: Int, canBeDividedBy divisor: Int) -> Bool {
            guard divisor != 0 else {
                fatalError("Divisor can'not be zero!")
            }
            return value % divisor == 0
        }
        
        queue.async {
            print("Started \(Date().timeIntervalSince1970)")
            DispatchQueue.concurrentPerform(iterations: self.nums.count) { index in
                let number = self.nums[index]
                if isNumber(number, canBeDividedBy: 13) {
//                    print("Current Thread: \(Thread.current)")
                    DispatchQueue.main.async {
                        self.divideds.append(number)
                    }
                }
            }
            
            print("Ended \(Date().timeIntervalSince1970)")
            DispatchQueue.main.async {
                print(self.divideds.map{ String($0) }.joined(separator: ", "))
            }
        }
        
    }

1kw數計算耗時
Start 1648028143.466887
Ended 1648028153.098256

DispatchSource

GCD 中提供了一個 DispatchSource 類,它可以幫你監聽系統底層一些對象的活動,例如這些對象: Mach port、Unix descriptor、Unix signal、VFS node,并允許你在這些活動發生時,向隊列提交一個任務以進行異步處理。

  • 示例:監聽指定目錄下文件變化(增、刪)
class DispatchSourceTest {
    var filePath: String
    var counter = 0
    let queue = DispatchQueue.global()
    
    init() {
        filePath = "\(NSTemporaryDirectory())"
        startObserve {
            print("File was changed")
        }
    }
    
    func startObserve(closure: @escaping () -> Void) {
        let fileURL = URL(fileURLWithPath: filePath)
        let monitoredDirectoryFileDescriptor = open(fileURL.path, O_EVTONLY)
        
        let source = DispatchSource.makeFileSystemObjectSource(
            fileDescriptor: monitoredDirectoryFileDescriptor,
            eventMask: .write, queue: queue)
        source.setEventHandler(handler: closure)
        source.setCancelHandler {
            close(monitoredDirectoryFileDescriptor)
        }
        source.resume()
    }
    
    func changeFile() {
        DispatchSourceTest.createFile(name: "DispatchSourceTest.md", filePath: NSTemporaryDirectory())
        counter += 1
        let text = "\(counter)"
        try! text.write(toFile: "\(filePath)/DispatchSourceTest.md", atomically: true, encoding: String.Encoding.utf8)
        print("file writed.")
    }
    
    static func createFile(name: String, filePath: String){
        let manager = FileManager.default
        let fileBaseUrl = URL(fileURLWithPath: filePath)
        let file = fileBaseUrl.appendingPathComponent(name)
        print("文件: \(file)")
        
        // 寫入 "hello world"
        let exist = manager.fileExists(atPath: file.path)
        if !exist {
            let data = Data(base64Encoded:"aGVsbG8gd29ybGQ=" ,options:.ignoreUnknownCharacters)
            let createSuccess = manager.createFile(atPath: file.path,contents:data,attributes:nil)
            print("文件創建結果: \(createSuccess)")
        }
    }
}

DispatchIO

DispatchIO 對象提供一個操作文件描述符的通道。簡單講你可以利用多線程異步高效地讀寫文件。

發起讀寫操作一般步驟如下:

  • 創建 DispatchIO 對象,或者說創建一個通道,并設置結束處理閉包。
  • 調用 read / write 方法
  • 調用 close 方法關閉通道
  • 在 close 方法后系統將自動調用結束處理閉包
let filePath: NSString = "test.zip"
// 創建一個可讀寫的文件描述符
let fileDescriptor = open(filePath.utf8String!, (O_RDWR | O_CREAT | O_APPEND), (S_IRWXU | S_IRWXG))
let queue = DispatchQueue(label: "com.sinkingsoul.DispatchQueueTest.serialQueue")
let cleanupHandler: (Int32) -> Void = { errorNumber in
}
let io = DispatchIO(type: .stream, fileDescriptor: fileDescriptor, queue: queue, cleanupHandler: cleanupHandler)
  • 設置閾值
io.setLimit(highWater: 1024*1024)
io.setLimit(lowWater: 1024*1024)
  • 讀操作
io.read(offset: 0, length: Int.max, queue: ioReadQueue) 
{ doneReading, data, error in 
    if (error > 0) {
            print("讀取發生錯誤了,錯誤碼:\(error)")
            return
        }
    if (data != nil) {
            // 使用數據
       }
    if (doneReading) {
           ioRead.close()
       }
}
  • 寫操作
io.write(offset: 0, data: data!, queue: ioWriteQueue)
 { doneWriting, data, error in 
    if (error > 0) {
        print("寫入發生錯誤了,錯誤碼:\(error)")
        return
    }
    if doneWriting {
        //...
        ioWrite.close()
    }
}
  • 示例:大文件合并
class DispatchIOTest {
    /// 利用很小的內存空間及同一隊列讀寫方式合并文件
    static func combineFileWithOneQueue() {
        let files: NSArray = ["/Users/xxx/Downloads/gcd.mp4.zip.001",
                              "/Users/xxx/Downloads/gcd.mp4.zip.002"]
        let outFile: NSString = "/Users/xxx/Downloads/gcd.mp4.zip"
        let ioQueue = DispatchQueue(
            label: "com.sinkingsoul.DispatchQueueTest.serialQueue")
        let queueGroup = DispatchGroup()
        
        let ioWriteCleanupHandler: (Int32) -> Void = { errorNumber in
            print("寫入文件完成 @\(Date())。")
        }
        
        let ioReadCleanupHandler: (Int32) -> Void = { errorNumber in
            print("讀取文件完成。")
        }
        
        let ioWrite = DispatchIO(type: .stream,
                                 path: outFile.utf8String!,
                                 oflag: (O_RDWR | O_CREAT | O_APPEND),
                                 mode: (S_IRWXU | S_IRWXG),
                                 queue: ioQueue,
                                 cleanupHandler: ioWriteCleanupHandler)
        ioWrite?.setLimit(highWater: 1024*1024)
        
//        print("開始操作 @\(Date()).")
        
        files.enumerateObjects { fileName, index, stop in
            if stop.pointee.boolValue {
                return
            }
            queueGroup.enter()
            
            let ioRead = DispatchIO(type: .stream,
                                    path: (fileName as! NSString).utf8String!,
                                    oflag: O_RDONLY,
                                    mode: 0,
                                    queue: ioQueue,
                                    cleanupHandler: ioReadCleanupHandler)
            ioRead?.setLimit(highWater: 1024*1024)
            
            print("開始讀取文件: \(fileName) 的數據")
            
            ioRead?.read(offset: 0, length: Int.max, queue: ioQueue) { doneReading, data, error in
                print("當前讀線程:\(Thread.current)--->")
                if (error > 0 || stop.pointee.boolValue) {
                    print("讀取發生錯誤了,錯誤碼:\(error)")
                    ioWrite?.close()
                    stop.pointee = true
                    return
                }
                
                if (data != nil) {
                    let bytesRead: size_t = data!.count
                    if (bytesRead > 0) {
                        queueGroup.enter()
                        ioWrite?.write(offset: 0, data: data!, queue: ioQueue) {
                            doneWriting, data, error in
                            print("當前寫線程:\(Thread.current)--->")
                            if (error > 0 || stop.pointee.boolValue) {
                                print("寫入發生錯誤了,錯誤碼:\(error)")
                                ioRead?.close()
                                stop.pointee = true
                                queueGroup.leave()
                                return
                            }
                            if doneWriting {
                                queueGroup.leave()
                            }
                            print("--->當前寫線程:\(Thread.current)")
                        }
                    }
                }
                
                if (doneReading) {
                    ioRead?.close()
                    if (files.count == (index+1)) {
                        ioWrite?.close()
                    }
                    queueGroup.leave()
                }
                print("--->當前讀線程:\(Thread.current)")
            }
            _ = queueGroup.wait(timeout: .distantFuture)
        }
    }
}

可以發現在讀寫過程中額外占用了 3M 左右內存,用時 2s 左右。這個結果中,內存占用比單隊列大(這個比較好理解),但速度還更慢了,性能瓶頸很有可能是在磁盤讀寫上。所以涉及文件寫操作時,并不是線程越多越快,要考慮傳輸速度、文件大小等因素。

DispatchData

DispatchData 對象可以管理基于內存的數據緩沖區。這個數據緩沖區對外表現為連續的內存區域,但內部可能由多個獨立的內存區域組成。
DispatchData 對象很多特性類似于 Data 對象,且 Data 對象可以轉換為 DispatchData 對象,而通過 DispatchIO 的 read 方法獲得的數據也是封裝為 DispatchData 對象的。

  • 首先將兩個文件轉換為 Data 對象,再轉換為 DispatchData 對象,然后拼接兩個對象為一個 DispatchData 對象,最后通過 DispatchIO 的 write 方法寫入文件中。看起來有多次的轉換過程,實際上 Data 類型讀取文件時支持虛擬隱射的方式,而 DispatchData 類型更是支持多個數據塊虛擬拼接,也不占用什么內存。

  • 示例:多文件合并

/// 利用 DispatchData 類型快速合并文件
static func combineFileWithDispatchData() {
    let filePathArray = ["/Users/xxx/Downloads/gcd.mp4.zip.001",
                          "/Users/xxx/Downloads/gcd.mp4.zip.002"]
    let outputFilePath: NSString = "/Users/xxx/Downloads/gcd.mp4.zip"
    let ioWriteQueue = DispatchQueue(
        label: "com.sinkingsoul.DispatchQueueTest.serialQueue")
    
    let ioWriteCleanupHandler: (Int32) -> Void = { errorNumber in
        print("寫入文件完成 @\(Date()).")
    }
    let ioWrite = DispatchIO(type: .stream,
                             path: outputFilePath.utf8String!,
                             oflag: (O_RDWR | O_CREAT | O_APPEND),
                             mode: (S_IRWXU | S_IRWXG),
                             queue: ioWriteQueue,
                             cleanupHandler: ioWriteCleanupHandler)
    ioWrite?.setLimit(highWater: 1024*1024*2)
    
    print("開始操作 @\(Date()).")
    
    // 將所有文件合并為一個 DispatchData 對象
    let dispatchData = filePathArray.reduce(DispatchData.empty) { data, filePath in
        // 將文件轉換為 Data
        let url = URL(fileURLWithPath: filePath)
        let fileData = try! Data(contentsOf: url, options: .mappedIfSafe)
        var tempData = data
        // 將 Data 轉換為 DispatchData
        let dispatchData = fileData.withUnsafeBytes {
            (u8Ptr: UnsafePointer<UInt8>) -> DispatchData in
            let rawPtr = UnsafeRawPointer(u8Ptr)
            let innerData = Unmanaged.passRetained(fileData as NSData)
            return DispatchData(bytesNoCopy:
                UnsafeRawBufferPointer(start: rawPtr, count: fileData.count),
                                deallocator: .custom(nil, innerData.release))
        }
        // 拼接 DispatchData
        tempData.append(dispatchData)
        return tempData
    }
    
    //將 DispatchData 對象寫入結果文件中
    ioWrite?.write(offset: 0, data: dispatchData, queue: ioWriteQueue) {
        doneWriting, data, error in
        if (error > 0) {
            print("寫入發生錯誤了,錯誤碼:\(error)")
            return
        }
        
        if data != nil {
//                print("正在寫入文件,剩余大小:\(data!.count) bytes.")
        }
        
        if (doneWriting) {
            ioWrite?.close()
        }
    }
}

可以發現在整個讀寫過程中幾乎沒有額外占用內存,速度很快在 1s 左右,這個讀寫方案堪稱完美,這要歸功于 DispatchData 的虛擬拼接和 DispatchIO 的分塊讀寫大小控制。這里順便提一下 DispatchIO 數據閥值上限 highWater,經過測試,如果設置為 1M,將耗時 4s 左右,設為 2M 及以上時,耗時均為 1s 左右,非常快速,而所有閥值的內存占用都很少。所以設置合理的閥值,對性能的改善也是有幫助的。

DispatchWorkItem

let workItem = DispatchWorkItem(qos: .default, flags: DispatchWorkItemFlags()) {
    // Do something
}
let queue = DispatchQueue.global()
queue.async(execute: workItem)
// 或
workItem.perform()
workItem.wait()
workItem.wait(timeout: DispatchTime) // 指定等待時間
workItem.wait(wallTimeout: DispatchWallTime) // 指定等待時間
  • 示例
func execute(work: @escaping ()->Void, completion: @escaping ()->Void) {
    let item = DispatchWorkItem(block: work)
    DispatchQueue.global().async(execute: item)
    item.wait()
    completion()
}

execute {
    print("[\(Date().timeIntervalSince1970)] I start sleeping and will finish after 5s...")
    Thread.sleep(forTimeInterval: 5)
} completion: {
    print("[\(Date().timeIntervalSince1970)] I'm done!")
}
/*
[1648089354.896827] I start sleeping and will finish after 5s...
[1648089359.901157] I'm done!
*/

參考:https://www.cnblogs.com/lxlx1798/articles/15040615.html
http://www.lxweimin.com/p/52457e86cfe0

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內容