Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/database/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios 我需要向简单变量添加线程锁定吗?_Ios_Swift_Multithreading_Thread Safety - Fatal编程技术网

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
    读入寄存器
  • 加1
  • someValue
    写回
  • 如果两个线程都在其中一个线程执行操作3之前执行操作1,那么与一个线程在另一个线程执行操作1之前执行所有三个操作相比,您将得到不同的答案

    还有更微妙的问题,优化编译器可能在一段时间内不会将修改后的值从寄存器写回(如果有的话)。此外,现代CPU有多个内核,每个内核都有自己的缓存。CPU将一个值写回内存并不保证它能直接进入内存。它可能会到达内核的缓存。您需要所谓的内存屏障,以确保所有内容都被整齐地写回主内存

    在更大范围内,您需要锁定以确保类中变量之间的一致性。因此,如果状态表示
    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)
        }
    }