Swift线程安全变量实现

为了保证线程安全操作,一般语言都提供了锁Lock来保证临界区安全。所以我们可以通过NSLock来实现对变量的读和写都加锁来实现线程安全。

class Sample {
 var intValue = 0
 let lock:NSLock = NSLock

  func  getValue()->Int {
     lock.lock()
     let value = intValue
     lock.unlock()
     return  value
 }
 
 func setValue(newValue:Int) {
     lock.lock()
     intValue = newValue
     lock.unlock()
 }

}

这样会有一个问题,如果需要线程安全保证的变量特别多,或者针对该变量的操作次数比较多,那么这种代码就需要写得比较多了,虽然可以定义一个函数把操作简洁点,但是还是不够优雅。
其实Swift里面还可以通过串行队列来保证线程安全,把针对变量的操作放到同一个队列中,并且以同步的方式来执行。

class QueueSample {
    let queue = DispatchQueue("com.liudong.demo")
    
    var intValue = 0
    
    func  getValue()->Int {
     return  queue.sync{
         intValue
     }
 }
 
    func setValue(newValue:Int) {
     queue.sync {
         intValue = newValue
     }
 }
    
}

这样写也能达到目标,但是还是有跟用lock相同的问题,就是模板代码多,某些情况下,可能还得建立不止一个队列。这里还存在一个问题,如果需要同时取值并设置值,例如自增+=1操作,就需要先get再set,代码比较难看。其实swift还有一个语法糖,PropertyWrapper,看下其漂亮的使用。

import Foundation

@propertyWrapper
class Atomic<Value> {
    
    var projectedValue:Atomic<Value> {
        return self
    }
    
    private let queue = DispatchQueue(label: "com.liudong.demo")

    private var value: Value
    
    init(wrappedValue: Value) {
        self.value = wrappedValue
    }
    
    var wrappedValue: Value {
        get {
            return queue.sync { value }
        }
        set {
            queue.sync { value = newValue }
        }
    }
    
    func mutate<R>(_ mutation: (inout Value) -> R) -> R {
        return queue.sync {
           return mutation(&value)
        }
    }
}

//使用如下
class MagicSample {
    @Atomic var intValue:Int = 0 
    
    func test() {
        DispatchQueque.global().async { [weak self] in
          guard let self = self else {return}
          //注意:如果实现intValue+=1来实现自增1的话,是不能直接写intValue+=1的,必须要使用mutate来保证原子操作。
            let value = self.$atomicValue.mutate { (v) -> Int in
                v += 1
                return v
            }
            
            if intValue == 1 {
                print("测试get")
            }
        }
    }
    
    
}

后续如果有其他的Property需要保证原子操作,只需要加上@Atomic注解即可,减少了模板代码的编写。

从代码编写的简洁度来说,DispatchQueue的方式有优势,下面来探究下两种方式性能上的差异。
性能对比方案:
让一个整数自增到给定值,比较两种方式的执行时长。

class ViewController: UIViewController {
    
    private var atomicBool = false
    
    @Atomic private var atomicValue = 0
    
    @Atomic private var atomicArray = [Int]()
    
    private let intLock = NSLock()
    
    private var lockInt = 0
    
    private let INCRETE_COUNT = 50000
    var start:Int? = nil
    var end:Int? = nil
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        self.view.addSubview(lockBtn)
        lockBtn.backgroundColor = UIColor.red
        lockBtn.frame = CGRect.init(x: 100, y: 100, width: 100, height: 100)
        self.view.addSubview(queueBtn )
        queueBtn.frame = CGRect.init(x: 100, y: 200, width: 100, height: 100)
        queueBtn.backgroundColor = UIColor.blue
        let lockGesture = UITapGestureRecognizer.init(target: self, action: #selector(testLock))
        lockBtn.addGestureRecognizer(lockGesture)
        let queueGesture = UITapGestureRecognizer.init(target: self, action: #selector(testQueue))
        queueBtn.addGestureRecognizer(queueGesture)
    }
    
    @objc private func testLock() {
        self.lockInt = 0
        start = Int(Date().timeIntervalSince1970 * 1000)
        for index in 0...INCRETE_COUNT {
            DispatchQueue.global().async { [weak self] in
                guard let self = self else {return}
                self.intLock.lock()
                self.lockInt+=1
                do {
                    //                    print("int value : \(self.lockInt)")
                    if self.lockInt == self.INCRETE_COUNT {
                        let end = Int(Date().timeIntervalSince1970 * 1000)
                        print("lock执行时间:\(end - self.start!)")
                    }
                    self.intLock.unlock()
                }
            }
        }
    }
    
    @objc private func testQueue() {
        self.atomicValue = 0
        start = Int(Date().timeIntervalSince1970 * 1000)
        for index in 0...INCRETE_COUNT {
            DispatchQueue.global().async { [weak self] in
                guard let self = self else {return}
                let value = self.$atomicValue.mutate { (v) -> Int in
                    v += 1
                    return v
                }
                if value == self.INCRETE_COUNT {
                    let end = Int(Date().timeIntervalSince1970 * 1000)
                    print("queue:执行时间:\(end - self.start!)")
                }
            }
        }
    }
    
    let lockBtn:UIButton  = {
        let btn = UIButton.init()
        btn.setTitle("Lock", for: .normal)
        return btn
    }()
    
    let queueBtn:UIButton  = {
        let btn = UIButton.init()
        btn.frame = CGRect.init(x: 100, y: 100, width: 100, height: 100)
        btn.setTitle("Queue", for: .normal)
        return btn
    }()
    
}

界面如下,红色按钮点击启动lock的方案,蓝色按钮点击启动queque方案
在这里插入图片描述

测试结果:
在这里插入图片描述

结论:lock性能更好,并且随着循环次数的增加,lock和queue的性能差距越来越大,循环次数达到5000的话,queue的耗时是lock的2倍。平时开发中,如果针对变量的多进程赋值取值到了1W加,可以考虑用锁来提升性能,操作次数1w一下的话,可以用队列,方便使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值