Ios 我需要向简单变量添加线程锁定吗?
假设我有一个对象,多个线程可以读取/写入Ios 我需要向简单变量添加线程锁定吗?,ios,swift,multithreading,thread-safety,Ios,Swift,Multithreading,Thread Safety,假设我有一个对象,多个线程可以读取/写入状态和someValue变量。如果这些变量是int、double、enum等类型,是否需要添加锁定 enum State: String { case one case two } class Object { var state: State var someValue: Double } 是的,你知道 想象一下两个线程试图向someValue添加1的情况。线程通过以下方式执行此操作: 将someValue读入寄存器 加1 将some
状态
和someValue
变量。如果这些变量是int、double、enum等类型,是否需要添加锁定
enum State: String {
case one
case two
}
class Object {
var state: State
var someValue: Double
}
是的,你知道
想象一下两个线程试图向someValue
添加1的情况。线程通过以下方式执行此操作:
someValue
读入寄存器someValue
写回someValue
的某些属性,例如,它是否为整数,则需要锁定以确保每个人始终具有一致的视图,即
someValue
状态
以上三个操作必须是原子的,或者如果对象在操作1之后被检查,但是在操作3之前,它将处于不一致的状态。
< P> JeremyP所说的,但是你也需要考虑更高的层次:你的“状态”和“SealValue”可能是相关的。因此,如果我改变状态,那么someValue,在我改变“状态”之后整个对象的内容可能是垃圾,因为新的状态与旧的someValue不匹配简单的解决方案是通过谷歌搜索如何在Swift中执行“@synchronized”,或者分派到主线程,或者分派到串行队列 为了模拟您的问题,我跟踪了以下代码片段(iOS应用程序环境): 对于第一个外观,期望值是:
myValue
的值应该是3000,因为addOneThousand()
已经被调用了三次,但是在我的机器(模拟器)上按顺序运行应用程序10次后,输出是:
1582年
1582
1582
1582年
三千
3000
3000
三千
2523
2523
2523
2523
2591
2591
2591
2591
1689年
1689
1689
1689年
1556
1556
1556
1556
1991年
1991
1991
1991年
1914年
1914
1914
1914年
2416
2416
2416
2416
1889年
1889
1889
1889年
最重要的是,每个结果的第四个值(等待延迟后的输出)在大多数情况下都是意外的(而不是3000)。如果我没有弄错的话,我假设我们现在面临的是一个挑战
对于这种情况,一个合适的解决方案是让线程的执行被序列化;编辑
addOneThousand()
(sync
而不是async
。您可能需要检查):
10次连续运行的输出为:
一千
2000
3000
三千
一千
2000
3000
三千
一千
2000
3000
三千
一千
2000
3000
三千
一千
2000
3000
三千
一千
2000
3000
三千
一千
2000
3000
三千
一千
2000
3000
三千
一千
2000
3000
三千
一千
2000
3000
三千
这代表了预期的结果
我希望它能有所帮助。需要锁定”需要与您期望的安全设置相一致。如果需要以协调的方式更新多个值,则肯定需要锁定。如果在多个线程上执行读/修改/写操作,则需要锁定或使用特殊的推测代码,以记录另一个线程的中断。为了简单地使用单个值,可以使用特殊的原子操作。有时仅仅设置一个值并不需要锁定,但这取决于具体情况。严格地说,不需要(尽管“是”也是一个有效的答案,并且对于一般情况来说可能是更正确的答案) 根据您的需要/需要,您可以使用原子操作,例如使用未锁定的
OSAtomicIncrement32
或OSAtomicCompareAndSwapPtr
函数
但是,请注意,即使一个操作是原子操作,两个单独的原子操作、连续操作也不是原子操作。因此,例如,如果您希望一致地更新状态
和某个值
,那么如果正确性很重要,那么不使用锁是完全不可能的(除非,巧合的是,它们足够小,因此您可以欺骗它们并将其压缩为一个更大的原子类型)
还要注意的是,即使您需要锁定或使用原子操作来确保程序的正确性,您偶尔也可以不这样做而“逃脱”。这是因为在大多数平台上,正确对齐内存地址的普通加载和存储都是原子的。但是,不要被诱惑,这并不像听起来的那么好,事实上根本不是一件好事——依赖于事情会正常工作(即使你“测试过”,它也会正常工作)会创建一种在开发过程中正常工作的程序,然后在发货后一个月会产生上千张支持票,没有明显的迹象表明出了什么问题。除非我弄错了,否则即使读取/写入单个属性也不一定是原子的,因此如果另一个线程同时写入,那么一个线程可能会读取垃圾。@MartinR我不知道。我的想法是,使用64位数据总线,
Double
将在一个内存写入周期内写入,但不知道enum
值。但是,在这样的问题上,
import UIKit
func delay (
_ seconds: Double,
queue: DispatchQueue = DispatchQueue.main,
after: @escaping ()->()) {
let time = DispatchTime.now() + Double(Int64(seconds * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
queue.asyncAfter(deadline: time, execute: after)
}
class ViewController: UIViewController {
var myValue = 0
override func viewDidLoad() {
super.viewDidLoad()
addOneThousand()
addOneThousand()
addOneThousand()
// calling this is just for logging the value after a delay
// just for making sure that all threads execution is completed...
delay(3.0) {
print(self.myValue)
}
}
func addOneThousand() {
DispatchQueue(label: "com.myapp.myqueue").async {
for _ in 0...999 {
self.myValue += 1
}
print(self.myValue)
}
}
}
func addOneThousand() {
DispatchQueue(label: "com.myapp.myqueue").sync {
for _ in 0...999 {
self.myValue += 1
}
print(self.myValue)
}
}