iOS 多线程之线程锁Swift-Demo示例总结

线程锁是什么

      在前面的文章中总结过多线程,总结了多线程之后,线程锁也是必须要好好总结的东西,这篇文章构思的时候可能写的东西得许多,只能挤时间一点点的慢慢的总结了,知道了线程之后要了解线程锁就得先了解一下什么是“线程锁”。

      “线程锁”一段代码在同一个时间内是只能被一个线程访问,为了避免在同一时间内有多个线程访问同一段代码就有了“锁”的概念,比如说,线程A在访问着一段代码,进入这段代码之后我们加了一个“锁”。这个时候线程B又来访问了,由于有了锁线程B就会等待线程A访问结束之后解开了“锁”线程B就可以接着访问这段代码了,这样就避免了在同一时间内多个线程访问同一段代码!

      相信上面的解释应该理解了“锁”的概念,也知道它是为了什么产生的,我们再举一个例子,一个房子一个人(线程)进去之后就把门锁上了,另一个人(线程)来了之后就在等待前面的人(线程)出来,等前面的人出来之后把门打开,它才可以进入房间。这样的解释相信应该明白了“锁”的概念,但是我们还是得强调一点,就是在这个“锁”和“解锁”之间不要有太多的“事”(执行代码,也就是任务)做,不然会造成过长时间的等待!就是去了多线程真正的含义和所用!

      下面我们一个个的来解释我们常用的线程锁。

NSLock

      NSLock是最简单的互斥锁,下面的NSCondition、NSConditionLock以及NSRecursiveLock都是遵守了NSLocking协议的,我们就放在一起说,包括我们现在说的NSLock也是,我们看看这个NSLock里面具体都有什么,先看看它代码里面的方法:

public protocol NSLocking {
    
    public func lock()

    public func unlock()
}

open class NSLock : NSObject, NSLocking {
    
    open func `try`() -> Bool

    open func lock(before limit: Date) -> Bool

    @available(iOS 2.0, *)
    open var name: String?
}

我们一个一个说说上面的方法:

      1、lock和unlock 就是这个类最常用的两个方法,“加锁”和“解锁”的方法。

      2、try()方法是尝试加锁,失败是不会阻塞线程的,如果获取锁失败就不会执行加锁代码。

      3、lock(before limit: Date) 这个方法是在后面参数传入的时间内没有获取到线程锁就阻塞线程,要是到期还没有获取到线程锁就唤醒线程,这时候返回值是NO。

下面是我们Demo中具体的使用的例子代码:

    var imageMutableArray:Array<Any> = Array.init()
    let lock = NSLock.init()

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        for  i in 0...1000 {
                
            imageMutableArray.append("imageArray==="+String(i))
        }
        print("你初始化的数组个数是",imageMutableArray.count )
    }
        
    // MARK: - startTestBtnAction
    override func removeFromDataImageArray() -> Void {
                
        // 我们使用多个线程去删除一个数组里面的东西,这样就有一个资源竞争的问题,我们看看
        // 你可以先把这里的lock加锁个解锁的方法注释掉,代码会崩溃在imageMutableArray.removeFirst()
        // 关于这样写(不加锁)时候的线程安全的问题  http://www.jianshu.com/p/2fce6a0bb17b
       
        while (true) {
                
                lock.lock()
                if (imageMutableArray.count > 0) {
                    
                    imageMutableArray.removeFirst()
                }else{
                 
                    now = CFAbsoluteTimeGetCurrent()
                    let resultString = "操作开始时间:" + String(describing: then) + "\n结束时间:"+String(describing: now) + "\n整个操作用时:"+String(now! - then!) + "ms"
                        /*
                           NOTE: 修改UI要在主线程,不能在子线程,刚开始疏忽报了下面的错误
                         */
                        DispatchQueue.main.async {
                           self.resulttextView.text = resultString
                        }
                    return
                }
                
                lock.unlock()
        }
    }

NSCondition

NSCondition条件锁,首先它也是遵循NSLocking协议的,这点和我们上面说的NSLock是一致的,所以它的加锁和解锁方式和我们前面说的NSLock是一样的,就是lock和unlock方法,你要是简单的使用它来解决线程同步的问题,那他简单的用法和前面写的NSLock也是一样的。但我们要是把NSCondition当NSLock用那就真的是浪费了!NSCondition还有自己的wait和signal用法,这个和后面信号量有点点类似,信号量的我们下面再说,看看NSCondition部分的代码:

    // MARK: - startTestBtnAction
     override func removeFromDataImageArray() -> Void {
        
        while (true) {
                
                lock.lock()
                if (imageMutableArray.count > 0) {
                        
                        imageMutableArray.removeFirst()
                }else{
                        
                        now = CFAbsoluteTimeGetCurrent()
                        let resultString = "操作开始时间:" + String(describing: then) + "\n结束时间:"+String(describing: now) + "\n整个操作用时:"+String(now! - then!) + "ms"

                        DispatchQueue.main.async {
                                
                                self.resulttextView.text = resultString
                        }
                        return
                }
                lock.unlock()
         }
    }

NSConditionLock

      NSConditionLock同样实现了NSLocking协议,不过测试一下之后你会发现这个新能比较低。NSConditionLock也能像NSCondition一样能进行线程之间的等待调用,并且还是线程安全的。下面使我们Demo中的代码,写给这里的只是最基本的加锁解锁的代码,先看看:

 var imageMutableArray:Array<Any> = Array.init()
    let lock = NSConditionLock.init()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Do any additional setup after loading the view.
        for  i in 0...1000 {
                
                imageMutableArray.append("imageArray==="+String(i))
        }
        print("NSCondition初始化的数组个数是",imageMutableArray.count )
                
    }
        
    // MARK: - startTestBtnAction
    override func removeFromDataImageArray() -> Void {
                
        while (true) {
                
                lock.lock()
                if (imageMutableArray.count > 0) {
                        
                        imageMutableArray.removeFirst()
                }else{
                        
                        now = CFAbsoluteTimeGetCurrent()
                        let resultString = "操作开始时间:" + String(describing: then) + "\n结束时间:"+String(describing: now) + "\n整个操作用时:"+String(now! - then!) + "ms"
                        
                        DispatchQueue.main.async {
                                
                                self.resulttextView.text = resultString
                        }
                        return
                }
                lock.unlock()
        }
    }

NSRecursiveLock

      有时候“加锁代码”中存在递归调用,递归开始前加锁,递归调用开始后会重复执行此方法以至于反复执行加锁代码最终造成死锁,这个时候可以使用递归锁来解决,也就是我们的NSRecursiveLock它就是递归锁!使用递归锁可以在一个线程中反复获取锁而不造成死锁,在这个过程中也会记录获取锁和释放锁的次数,只有等两者平衡的时候才会释放,下面是我们Demo中的示例:       

 // 递归调用
    func removeFromImageArray() -> Void {
        
        recursiveLock.lock()
        if (imageMutableArray.count>0) {
                
            imageMutableArray.removeFirst()
            self.removeFromImageArray()
        }
        recursiveLock.unlock()
    }
    
        
    // MARK: - removeFromDataImageArray
    // 模仿递归调用
    override func removeFromDataImageArray() -> Void {
        
        let dispatchGroup  = DispatchGroup.init()
        let dispatchQueue  = DispatchQueue.init(label:queueLabel, qos: .default, attributes: .concurrent)
        dispatchQueue.async(group: dispatchGroup, qos: .default, flags: DispatchWorkItemFlags.noQoS) {
                
            self.removeFromImageArray()
        }
        
        dispatchGroup.notify(queue: DispatchQueue.main) {
                
            self.now = CFAbsoluteTimeGetCurrent()
            let resultString = "操作开始时间:" + String(describing: self.then) + "\n结束时间:"+String(describing: self.now) + "\n整个操作用时:"+String(self.now! - self.then!) + "ms"
            self.resulttextView.text = resultString
        }
    }

@synchronized

      @synchronized你要说它简单,它的用法的确都是比较简单的,要想深了探究一下它的话,它里面的东西还的确是挺多的!但我们是在Swift中来讨论线程锁的,这里也就不能再使用 @synchronized,因为在Swift中它是不在使用了的,相应代替它的是下面下面这两句:objc_sync_enter()  中间是你需要加锁的代码  objc_sync_exit() ,那上面相同的操作我们用这个互斥锁写的话代码如下:

 // MARK: - removeFromDataImageArray
    override func removeFromDataImageArray() -> Void {
        
        while (true) {
                //互斥锁
                objc_sync_enter(self)
                if (imageMutableArray.count > 0) {
                        
                        imageMutableArray.removeFirst()
                }else{
                        
                        now = CFAbsoluteTimeGetCurrent()
                        let resultString = "操作开始时间:" + String(describing: then) + "\n结束时间:"+String(describing: now) + "\n整个操作用时:"+String(now! - then!) + "ms"
                        DispatchQueue.main.async {
                                
                                self.resulttextView.text = resultString
                        }
                        return
                }
                objc_sync_exit(self)
        }
    }

dispatch_semaphore_t 信号量

      dispatch_semaphore_t是属于GCD里面的东西,在前面终结多线程的时候我们说把它放在我们总结线程锁的时候说,在这里我们就说一些这个信号量,dispatch_semaphore_t 和前面@synchronized一样都是我们OC的写法,在我们的Swift中也不是这样写的,全部的内容都是在DispatchSemaphore中,关于GCD方面API的对比我们在下面做了一张表,大致的说一下:

      你看完了这张图的对比以及总结之后,我们说回我们的主题:DispatchSemaphore 可以看到它的主要的方法:

open class DispatchSemaphore : DispatchObject {
}

/// dispatch_semaphore
extension DispatchSemaphore {
    // 发送信号,让信号量+1方法
    public func signal() -> Int
    // 等待,让信号量-1方法
    public func wait()
    // 下面两个方法可以设置等待的时间,过了这个时间要是没有让信号量大于或者等于初始化的信号量值的时候
    // 就会自己接着往执行代码,相等于我们的锁是失效了的
    public func wait(timeout: DispatchTime) -> DispatchTimeoutResult

    public func wait(wallTimeout: DispatchWallTime) -> DispatchTimeoutResult
}
extension DispatchSemaphore {

    
    /*!
     * @function dispatch_semaphore_create
     *
     * @abstract
     * Creates new counting semaphore with an initial value.
     *
     * @discussion
     * Passing zero for the value is useful for when two threads need to reconcile
     * the completion of a particular event. Passing a value greater than zero is
     * useful for managing a finite pool of resources, where the pool size is equal
     * to the value.
     *
     * @param value
     * The starting value for the semaphore. Passing a value less than zero will
     * cause NULL to be returned.
     *
     * @result
     * The newly created semaphore, or NULL on failure.
     */
    @available(iOS 4.0, *)
    public /*not inherited*/ init(value: Int)
}

       OC和Swift的用法是一样的,只是在写法上有一些的区别,这里就不再说OC的了,我们直接看看Swift的代码怎么写:

// MARK: - startTestBtnAction
     override func removeFromDataImageArray() -> Void {
                
        while (true) {
                
                /* 也可以直接写: semaPhore.wait()
                   这里发生一个等待信号,信号量就-1,变成0 ,后面的任务就会处于等待状态,
                   等到信号量大于等于1的时候在执行,要是信号量不大于或者等于你初始化时候的值,它就一直处于等待状态
                   当然,你也可以在这里这是等待的时间  semaPhore.wait(timeout: DispatchTime.init(uptimeNanoseconds: UInt64(10)))
                   但过了这个时间之后在进入就等于是我们的锁失效了。面临的问题也就是相应的崩溃,在删除方法哪里,可以自己试一下
                 */
                _ = semaPhore.wait(timeout: DispatchTime.distantFuture)
                
                if (imageMutableArray.count > 0) {
                        
                        imageMutableArray.removeFirst()
                }else{
                        
                        now = CFAbsoluteTimeGetCurrent()
                        let resultString = "操作开始时间:" + String(describing: then) + "\n结束时间:"+String(describing: now) + "\n整个操作用时:"+String(now! - then!) + "ms"
                        
                        DispatchQueue.main.async {
                                
                                self.resulttextView.text = resultString
                        }
                        
                        // 不要忘记在这里加处理,不然return之后是执行不到下面的semaPhore.signal()代码
                        semaPhore.signal()
                        return
                }
                
                // signal() 方法,这里会使信号量+1
                   semaPhore.signal()
        }
    }

POSIX 

      POSIX和我们前面写的dispatch_semaphore_t用法是挺像的,但探究本质的haul它们根本就不是一个东西,POSIX是Unix/Linux平台上提供的一套条件互斥锁的API。你要是在OC的文件中只用的话你需要导入头文件:pthread.h

在Swift中就不用了,但是在使用的时候不管是OC的还是Swift的,代码是一致的,它的几个主要的方法就是下面三个,剩下的具体的代码可以看demo或者是下面基本的方法:

1、pthread_mutex_init        初始化方法

2、pthread_mutex_lock      加锁方法

3、pthread_mutex_unlock   解锁方法

class POSIXController: ThreadLockController {

    var imageMutableArray:Array<Any> = Array.init()

    var mutex:pthread_mutex_t = pthread_mutex_t()     //初始化pthread_mutex_t类型变量

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 初始化
        pthread_mutex_init(&mutex,nil)
        // Do any additional setup after loading the view.
        for  i in 0...1000 {
                
                imageMutableArray.append("imageArray==="+String(i))
        }
        print("NSLock初始化的数组个数是",imageMutableArray.count)
    }
    // MARK: - startTestBtnAction
    override func removeFromDataImageArray() -> Void {
        
        while (true) {
                
                // 加锁
                pthread_mutex_lock(&mutex)
                if (imageMutableArray.count > 0) {
                        
                        imageMutableArray.removeFirst()
                }else{
                        
                        now = CFAbsoluteTimeGetCurrent()
                        let resultString = "操作开始时间:" + String(describing: then) + "\n结束时间:"+String(describing: now) + "\n整个操作用时:"+String(now! - then!) + "ms"
                      
                        DispatchQueue.main.async {
                                
                                self.resulttextView.text = resultString
                        }
                        pthread_mutex_unlock(&mutex);
                        return
                }
                // 解锁
                pthread_mutex_unlock(&mutex)
        }
    }
    /*
         Swift 的deinit函数实际的作用和OC中的dealloc函数是一样的
         对象的释放  通知  代理等等的处理都是在这里处理的
    */
    deinit {
        pthread_mutex_destroy(&mutex);  //释放该锁的数据结构
    }
}

剩下的还有什么

      1、OSSpinLock   

      首先要提的是OSSpinLock已经出现了BUG,导致并不能完全保证是线程安全,所以这个我们知道,大概的了解一下,具体的问题可以去这里仔细看看:不再安全的 OSSpinLock

      2、dispatch_barrier_async/dispatch_barrier_sync

      这个我在前面总结GCD的时候说过了这个“栅栏”函数,就不在这里重复说了

      3、最后就是Demo的地址了,这个Demo原本是想用Swift试着模仿一下微信的UI的,包括聊天框架那部分,以前写过OC的,这次春被用Swift写一下,主要也是为了用一下Swift,以及看一下4.0它的一些新的特性,不然很久不写,一些东西比较容易遗忘!  

DemoGit地址

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏屈定‘s Blog

设计模式--动态代理的思考

在一些第三方框架中经常能看到动态代理的案例,尤其是RPC框架,ORM框架等,该篇将分析这些实现的原理,另外延伸在业务中的使用示例.

14130
来自专栏Java架构沉思录

如何优雅实现优雅停机?

其实优雅停机,就是在要关闭服务之前,不是立马全部关停,而是做好一些善后操作,比如:关闭线程、释放连接资源等。

20910
来自专栏小灰灰

SpringMVC返回图片的几种方式

SpringMVC返回图片的几种方式 后端提供服务,通常返回的json串,但是某些场景下可能需要直接返回二进制流,如一个图片编辑接口,希望直接将图片流返回给前...

749100
来自专栏Jackson0714

02.你真的知道线程安全的“单件模式”吗?

29070
来自专栏码洞

基于Netty实现Redis协议的编码解码器

上面是Netty的服务器端基本消息处理结构,为了便于初学者理解,它和真实的结构有稍许出入。Netty是基于NIO的消息处理框架,用来高效处理网络IO。处理网络消...

29910
来自专栏大内老A

ASP.NET Core的配置(4):多样性的配置来源[中篇]

我们在本篇文章中会介绍三种针对物理文件的ConfiguationProvider,它们分别是针对JSON文件的JsonConfiguationProvider,...

20980
来自专栏架构技术

AOP缓存实现

17940
来自专栏猿份到

LeakCanary源码浅析

在Android开发中最让人们头疼的就是内存泄漏了,今天来介绍一个查看内存是否泄漏的工具LeakCanary,并通过研究源码明白它是如何分析和查找存在泄漏信息的...

35850
来自专栏木木玲

Netty in Action ——— The codec framework

14420
来自专栏编程

如何修改动态代理的私有变量

最近在写一个 Spring Controller 的 JUnit 单元测试时,需要将一个Mock对象塞入到Controller的私有成员变量中,发现怎么都塞不成...

20990

扫码关注云+社区

领取腾讯云代金券