Swift全局变量线程安全

时间:2018-02-24 22:46:18

标签: swift multithreading global-variables

我知道,全局变量“不性感”,但我目前的项目中很少。我玩过Xcode的Thread Sanitizer并发现了数据竞争。所以我试着让它们变得安全。

因为我还想要对这个变量进行单点管理。我试图在getter和变量的setter中做GCD的东西。

最后我找到了一个有效的解决方案,被编译器接受并且Thread Sanitizer很高兴......但是......这个解决方案看起来很丑陋(见下文)并且非常慢(做了性能测试并且它很慢)。

是的,我知道,如果我为此使用类,它可能更“swifty”,但是对于线程安全的全局变量必须有一个简单的解决方案。

那么你会如此善良并给出提示和建议来优化这种尝试吗?欢迎Anyt提示/想法/建议/评论!

j

2 个答案:

答案 0 :(得分:1)

OOPer让我重做性能测试,因为结果很奇怪。

嗯,他是对的。我写了一个简单的应用程序(Performance Test App on GitHub)并附上了一些截图。

我在带有最新IOS的iPhone SE上运行测试。该应用程序是在设备上启动的,而不是在Xcode中启动的。所有显示的测试结果的编译器设置都是“debug”。我也测试了“完全优化”(最小的最快[-Os]),但结果非常相似。我认为在简单的测试中并没有太多优化。

测试应用程序只运行上面答案中描述的测试。为了使它更加真实,它正在使用qos类.userInteractive,.default和.background对三个并行的异步调度队列进行每个测试。

可能有更好的方法来测试这些东西。但就这个问题而言,我认为这已经足够了。

如果有人重新评估代码并且可能找到更好的测试算法,我很高兴......我们都会从中学习。我现在停止了我的工作。

我的眼睛结果很奇怪。所有三种不同的方法都提供了大致相同的性能每次跑步都有另一个“英雄”,所以我认为它只受其他背景任务等的影响。所以即使是Itai Ferber“漂亮”的解决方案在实践中也没有任何好处。它只是一个更优雅的代码。

是的,线程保存解决方案比未排队的解决方案慢。

这是主要的学习:是的,可以使全局变量线程安全,但是它存在严重的性能问题。

Test with 100 iterations

Test with 1_000 iterations

Test with 10_000 iterations

Test with 100_000 iterations

答案 1 :(得分:0)

编辑:我留下第一个答案来保留历史记录,但是一些OOPer的暗示导致了完全不同的观点(见下一个答案)。

首先:我对答案流入的速度和受过良好教育印象非常深刻(我们是在周末!)

所以Itai Ferber的建议非常好,正如他所说,我做了一些性能测试,只是为了给他一些回报; - )

我在操场上用附加的代码运行测试。正如您所看到的,这是迄今为止不是一个设计良好的性能测试,它只是一个简单的测试,以获得性能影响的要点。我做了几次迭代(见下表)。

再说一次:我是在游乐场做的,所以绝对的时间会更好一些真实的"应用程序,但测试之间的差异将非常相似。

主要发现:

  • 互动显示线性行为(如预期的那样)

  • "我"解决方案(test1)比"未排队"慢约15倍。全局变量(test0)

  • 我做了一个测试,我使用了一个额外的全局变量作为辅助变量(test2),这稍微快一些,但不是真正的突破

  • 来自Itai Ferber(test3)的建议解决方案比纯全局变量(test0)慢大约6到7倍......所以它的速度是" my"解决方案

所以替代3不仅看起来更好,因为它不需要辅助变量的开销也更快。

Results of Performance Tests

// the queue to synchronze data access, it's a concurrent one
fileprivate let globalDataQueue = DispatchQueue(
    label: "com.ACME.globalDataQueue",
    attributes: .concurrent)

// ------------------------------------------------------------------------------------------------
// Base Version: Just a global variable
// this is the global "variable"  we worked with
var globalVariable : Int = 0


// ------------------------------------------------------------------------------------------------
// Alternative 1:  with concurrent queue, helper variable insider getter
// As I used a calculated variable, to overcome the compiler errors, we need a helper variable
// to store the actual value.
var globalVariable1_Value : Int = 0

// this is the global "variable"  we worked with
var globalVariable1 : Int {
    set (newValue) {
        globalDataQueue.async(flags: .barrier) {
            globalVariable1_Value = newValue
        }
    }

    get {
        // we need a helper variable to store the result.
        // inside a void closure you are not allow to "return"
        var globalVariable1_Helper : Int = 0
        globalDataQueue.sync{
            globalVariable1_Helper = globalVariable1_Value
        }
        return globalVariable1_Helper
    }
}

// ------------------------------------------------------------------------------------------------
// Alternative 2:  with concurrent queue, helper variable as additional global variable
// As I used a calculated variable, to overcome the compiler errors, we need a helper variable
// to store the actual value.
var globalVariable2_Value : Int = 0
var globalVariable2_Helper : Int = 0

// this is the global "variable"  we worked with
var globalVariable2 : Int {

    // the setter
    set (newValue) {
        globalDataQueue.async(flags: .barrier) {
            globalVariable2_Value = newValue
        }
    }

    // the getter
    get {
        globalDataQueue.sync{
            globalVariable2_Helper = globalVariable2_Value
        }
        return globalVariable2_Helper
    }
}

// ------------------------------------------------------------------------------------------------
// Alternative 3:  with concurrent queue, no helper variable as Itai Ferber suggested
// "compact" design
var globalVariable3_Value : Int = 0
var globalVariable3 : Int {

    set (newValue) { 
        globalDataQueue.async(flags: .barrier) { globalVariable3_Value = newValue } 
    }

    get { 
        return globalDataQueue.sync { globalVariable3_Value } 
    }
}


// ------------------------------------------------------------------------------------------------
// -- Testing
// variable for read test
var testVar = 0
let numberOfInterations = 2


// Test 0
print ("\nStart test0: simple global variable, not thread safe")
let startTime = CFAbsoluteTimeGetCurrent()

for _ in 0 ..< numberOfInterations {
    testVar = globalVariable
    globalVariable += 1
}

let endTime = CFAbsoluteTimeGetCurrent()
let timeDiff = endTime - startTime
print("globalVariable == \(globalVariable), test0 time needed \(timeDiff) seconds")


// Test 1
testVar = 0
print ("\nStart test1: concurrent queue, helper variable inside getter")
let startTime1 = CFAbsoluteTimeGetCurrent()

for _ in 0 ..< numberOfInterations {
    testVar = globalVariable1
    globalVariable1 += 1
}

let endTime1 = CFAbsoluteTimeGetCurrent()
let timeDiff1 = endTime1 - startTime1
print("globalVariable == \(globalVariable1), test1 time needed \(timeDiff1) seconds")


// Test 2
testVar = 0
print ("\nStart test2: with concurrent queue, helper variable as an additional global variable")
let startTime2 = CFAbsoluteTimeGetCurrent()

for _ in 0 ..< numberOfInterations {
    testVar = globalVariable2
    globalVariable2 += 1
}

let endTime2 = CFAbsoluteTimeGetCurrent()
let timeDiff2 = endTime2 - startTime2
print("globalVariable == \(globalVariable2), test2 time needed \(timeDiff2) seconds")


// Test 3
testVar = 0
print ("\nStart test3: with concurrent queue, no helper variable as Itai Ferber suggested")
let startTime3 = CFAbsoluteTimeGetCurrent()

for _ in 0 ..< numberOfInterations {
    testVar = globalVariable3
    globalVariable3 += 1
}

let endTime3 = CFAbsoluteTimeGetCurrent()
let timeDiff3 = endTime3 - startTime3
print("globalVariable == \(globalVariable3), test3 time needed \(timeDiff3) seconds")