Swift 線程安全數(shù)組

原文地址

有并發(fā)的地方就存在線程安全問題,尤其是對(duì)于 Swift 這種還沒有內(nèi)置并發(fā)支持的語言來說線程安全問題更為突出。下面我們通過常見的數(shù)組操作來分析其中存在的線程問題,以及如何實(shí)現(xiàn)一個(gè)線程安全數(shù)組。

問題所在

因?yàn)闊o法確定執(zhí)行順序,所以并發(fā)導(dǎo)致的問題一般都很難模擬和測(cè)試。不過我們可以通過下面這段代碼來模擬一個(gè)并發(fā)情形下導(dǎo)致的數(shù)據(jù)競(jìng)爭(zhēng)問題。

var array = [Int]()

DispatchQueue.concurrentPerform(iterations: 1000) { index in
    let last = array.last ?? 0
    array.append(last + 1)
}復(fù)制代碼

這段代碼中我們對(duì)數(shù)組 array 進(jìn)行了 1000 次并發(fā)修改操作,雖然有些夸張但是它能很好的揭示一些并發(fā)環(huán)境下數(shù)組寫操作存在的一些問題。因?yàn)閷?duì)于值類型來說 Swift 采用的是 Copy On Write 機(jī)制,所以在進(jìn)行 Copy On Write 處理是可能數(shù)組已經(jīng)被另一個(gè)寫操作給修改了。這就造成了數(shù)組中元素和數(shù)據(jù)的丟失現(xiàn)象,如下:

Unsafe loop count: 988.
Unsafe loop count: 991.
Unsafe loop count: 986.
Unsafe loop count: 995.復(fù)制代碼

串行隊(duì)列

這應(yīng)該是大家都能想到的一種最常見處理方式。 由于串行隊(duì)列每次都只能運(yùn)行一個(gè)進(jìn)程,所以即使有多個(gè)數(shù)組寫操作進(jìn)程我們也能確保資源的互斥訪問。這樣數(shù)組是從設(shè)計(jì)的并發(fā)進(jìn)程安全的。

let queue = DispatchQueue(label: "SafeArrayQueue")

queue.async() {
  // 寫操作
}

queue.sync() {
  // 讀操作
}復(fù)制代碼

由于寫操作并不需要返回操作結(jié)果,所有這里可以使用異步的方式進(jìn)行。而對(duì)于讀操作來說則必須采用同步的方式實(shí)時(shí)返回操作結(jié)果。但是串行隊(duì)列有一個(gè)最為明顯的缺陷:多個(gè)讀操作之間也是互斥的。很顯然這種方式太過粗暴存在明顯的性能問題,畢竟讀操作的頻率直覺上是要高過寫操作的。

并發(fā)隊(duì)列

采用并發(fā)隊(duì)列我們就可以很好的解決上面提到的多個(gè)讀操作的性能問題,不過隨之而來的就是寫操作的數(shù)據(jù)競(jìng)爭(zhēng)。這與我們?cè)趯W(xué)習(xí)操作系統(tǒng)是的 讀者-作者 問題本質(zhì)上是一類問題,我們可以通過共享互斥鎖來解決寫操作的數(shù)據(jù)競(jìng)爭(zhēng)問題。對(duì)于 iOS 來說它就是 GCD 中的寫欄柵 barrier 機(jī)制。

[圖片上傳中...(image-407a8e-1569391139400-0)]

<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;">Barrier</figcaption>

let queue = DispatchQueue(label: "SafeArrayQueue", attributes: .concurrent)

queue.async(flags: .barrier) {
  // 寫操作
}

queue.sync() {
  // 讀操作
}復(fù)制代碼

上面代碼中我們對(duì)異步的寫操作設(shè)置了 barrier 標(biāo)示,這意味著在執(zhí)行異步操作代碼的時(shí)候隊(duì)列不能執(zhí)行其他代碼。而對(duì)于同步的讀操作來說,由于是并發(fā)隊(duì)列同時(shí)讀取數(shù)據(jù)并不會(huì)存在任何性能問題。

實(shí)踐

/// A thread-safe array.
public class SafeArray<Element> {
    fileprivate let queue = DispatchQueue(label: "Com.BigNerdCoding.SafeArray", attributes: .concurrent)
    fileprivate var array = [Element]()
}

// MARK: - Properties
public extension SafeArray {

    var first: Element? {
        var result: Element?
        queue.sync { result = self.array.first }
        return result
    }

    var last: Element? {
        var result: Element?
        queue.sync { result = self.array.last }
        return result
    }

    var count: Int {
        var result = 0
        queue.sync { result = self.array.count }
        return result
    }

    var isEmpty: Bool {
        var result = false
        queue.sync { result = self.array.isEmpty }
        return result
    }

    var description: String {
        var result = ""
        queue.sync { result = self.array.description }
        return result
    }
}

// MARK: - 讀操作
public extension SafeArray {
    func first(where predicate: (Element) -> Bool) -> Element? {
        var result: Element?
        queue.sync { result = self.array.first(where: predicate) }
        return result
    }

    func filter(_ isIncluded: (Element) -> Bool) -> [Element] {
        var result = [Element]()
        queue.sync { result = self.array.filter(isIncluded) }
        return result
    }

    func index(where predicate: (Element) -> Bool) -> Int? {
        var result: Int?
        queue.sync { result = self.array.index(where: predicate) }
        return result
    }

    func sorted(by areInIncreasingOrder: (Element, Element) -> Bool) -> [Element] {
        var result = [Element]()
        queue.sync { result = self.array.sorted(by: areInIncreasingOrder) }
        return result
    }

    func flatMap<ElementOfResult>(_ transform: (Element) -> ElementOfResult?) -> [ElementOfResult] {
        var result = [ElementOfResult]()
        queue.sync { result = self.array.flatMap(transform) }
        return result
    }

    func forEach(_ body: (Element) -> Void) {
        queue.sync { self.array.forEach(body) }
    }

    func contains(where predicate: (Element) -> Bool) -> Bool {
        var result = false
        queue.sync { result = self.array.contains(where: predicate) }
        return result
    }
}

// MARK: - 寫操作
public extension SafeArray {

    func append( _ element: Element) {
        queue.async(flags: .barrier) {
            self.array.append(element)
        }
    }

    func append( _ elements: [Element]) {
        queue.async(flags: .barrier) {
            self.array += elements
        }
    }

    func insert( _ element: Element, at index: Int) {
        queue.async(flags: .barrier) {
            self.array.insert(element, at: index)
        }
    }

    func remove(at index: Int, completion: ((Element) -> Void)? = nil) {
        queue.async(flags: .barrier) {
            let element = self.array.remove(at: index)

            DispatchQueue.main.async {
                completion?(element)
            }
        }
    }

    func remove(where predicate: @escaping (Element) -> Bool, completion: ((Element) -> Void)? = nil) {
        queue.async(flags: .barrier) {
            guard let index = self.array.index(where: predicate) else { return }
            let element = self.array.remove(at: index)

            DispatchQueue.main.async {
                completion?(element)
            }
        }
    }

    func removeAll(completion: (([Element]) -> Void)? = nil) {
        queue.async(flags: .barrier) {
            let elements = self.array
            self.array.removeAll()

            DispatchQueue.main.async {
                completion?(elements)
            }
        }
    }
}

public extension SafeArray {

    subscript(index: Int) -> Element? {
        get {
            var result: Element?

            queue.sync {
                guard self.array.startIndex..<self.array.endIndex ~= index else { return }
                result = self.array[index]
            }

            return result
        }
        set {
            guard let newValue = newValue else { return }

            queue.async(flags: .barrier) {
                self.array[index] = newValue
            }
        }
    }
}

// MARK: - Equatable
public extension SafeArray where Element: Equatable {

    func contains(_ element: Element) -> Bool {
        var result = false
        queue.sync { result = self.array.contains(element) }
        return result
    }
}

// MARK: - 自定義操作符
public extension SynchronizedArray {

    static func +=(left: inout SynchronizedArray, right: Element) {
        left.append(right)
    }

    static func +=(left: inout SynchronizedArray, right: [Element]) {
        left.append(right)
    }
}復(fù)制代碼

通過 filePrivate 屬性 arrayqueueSafeArray 成功的實(shí)現(xiàn)了大多數(shù)數(shù)組常用功能,更為關(guān)鍵的是該類型并發(fā)安全:所有的寫操作都通過 barrier 方式的異步進(jìn)行,而讀操作則與內(nèi)置 Array 沒有什么區(qū)別。

需要注意的是:我們使用同樣的方式可以實(shí)現(xiàn)并發(fā)安全的 Dictionary 類似:SynchronizedDictionary。

接下來,我們可以對(duì)傳統(tǒng)的非并發(fā)安全數(shù)組和 SafeArray 進(jìn)行以下比較:

import Foundation
import PlaygroundSupport

// Thread-unsafe array
do {
    var array = [Int]()
    var iterations = 1000
    let start = Date().timeIntervalSince1970

    DispatchQueue.concurrentPerform(iterations: iterations) { index in
        let last = array.last ?? 0
        array.append(last + 1)

        DispatchQueue.global().sync {
            iterations -= 1

            // Final loop
            guard iterations <= 0 else { return }
            let message = String(format: "Unsafe loop took %.3f seconds, count: %d.",
                Date().timeIntervalSince1970 - start,
                array.count)
            print(message)
        }
    }
}

// Thread-safe array
do {
    var array = SafeArray<Int>()
    var iterations = 1000
    let start = Date().timeIntervalSince1970

    DispatchQueue.concurrentPerform(iterations: iterations) { index in
        let last = array.last ?? 0
        array.append(last + 1)

        DispatchQueue.global().sync {
            iterations -= 1

            // Final loop
            guard iterations <= 0 else { return }
            let message = String(format: "Safe loop took %.3f seconds, count: %d.",
                Date().timeIntervalSince1970 - start,
                array.count)
            print(message)
        }
    }
}

PlaygroundPage.current.needsIndefiniteExecution = true復(fù)制代碼

得到的輸出可能如下:

Unsafe loop took 1.031 seconds, count: 989.
Safe loop took 1.363 seconds, count: 1000.復(fù)制代碼

雖然由于使用了 GCD 機(jī)制導(dǎo)致速度慢了 30% 左右并且使用了更多的內(nèi)存,但是與之對(duì)應(yīng)的是我們實(shí)現(xiàn)了一個(gè)并發(fā)安全的數(shù)組類型。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 貓頭mt閱讀 114評(píng)論 0 2
  • 2009年的十一,我和同學(xué)一時(shí)興起,奔去了伊春。因?yàn)槁犝f秋天的森林最好看,千里林海,五顏六色,在陽光的照耀下美不勝...
    何有理閱讀 1,310評(píng)論 0 2
  • 已然忘懷 邂逅那抹黃 于何處何年 此刻我卻明了 它絕不是帝王朝堂的無上冠冕 亦不是泥沙俱下的母親河原色 有別于傳遞...
    一瀟而過閱讀 293評(píng)論 0 0
  • 跟崗學(xué)校:上海市格致中學(xué) 讀書就是讀人生 ——兼議宋、連兩堂語文示范課 ...
    cjq7777閱讀 1,343評(píng)論 0 5
  • “寧可從艱苦中走出來,而不是向艱苦中走過去?!?所以,人生之初受一點(diǎn)艱苦與磋磨是應(yīng)當(dāng)?shù)?,是必須的?面對(duì)現(xiàn)在生活的...
    和塵之塵閱讀 315評(píng)論 0 2

友情鏈接更多精彩內(nèi)容