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地址

时间: 2024-10-04 16:47:02

iOS 多线程之线程锁Swift-Demo示例总结的相关文章

iOS多线程技术—线程的状态

iOS多线程技术—线程的状态 一.简单介绍 线程的创建: self.thread=[[NSThread alloc]initWithTarget:self selector:@selector(test) object:nil]; 说明:创建线程有多种方式,这里不做过多的介绍. 线程的开启: [self.thread start]; 线程的运行和阻塞: (1)设置线程阻塞1,阻塞2秒 [NSThread sleepForTimeInterval:2.0]; (2)第二种设置线程阻塞2,以当前时间

iOS开发多线程篇---线程锁(线程安全)

@interface BTThreadViewController () {     NSThread *OneThread;//师傅一     NSThread *TwoThread;//师傅二     NSThread *ThreeThread;//师傅三     int allCake;//蛋糕总数 } @end @implementation BTThreadViewController - (id)initWithNibName:(NSString *)nibNameOrNil bun

IOS 多线程,线程同步的三种方式

一般情况下我们使用线程,在多个线程共同访问同一块资源.为保护线程资源的安全和线程访问的正确性. 在IOS中我们一般情况下使用以下三种线程同步代码方式: 第一种和第二种代码同步的使用方法,一般情况下我们只需要使用NSLock和NSCondition申明2个属性.然后给此属性赋对应的值.那么即可作为安全防控的线程手段. 同时也可以保证线程的资源安全. 1:NSLock方式 [xxxlock   lock] //上锁 同步代码块 [xxxlock   unlock]//解锁 2:NSCondition

iOS多线程技术—线程间的通信

iOS开发多线程篇—线程间的通信 一.简单说明 线程间通信:在1个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信 线程间通信的体现 1个线程传递数据给另1个线程 在1个线程中执行完特定任务后,转到另1个线程继续执行任务 线程间通信常用方法 - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait; - (void)performSelector:(SE

python多线程之线程锁二(同一时间一个线程获得2把线程锁)

#coding:utf-8 '''线程锁''' import threading import time num = 0 #全局变量 num2 = 0 def runs():     time.sleep(1)     global num #在函数内部要对全局变量进行更改,需要进行声明     global num2     lock.acquire() #在操作时锁住,防止其他线程在同一时间对num变量进行加1,从而确保数据在同一时间确保只有一个线程对它进行更改,不然造成数据不正确     

iOS 数据持久化之KeyChain(Swift Demo)

原创blog,转载请注明出处 blog.csdn.net/hello_hwc?viewmode=list 前言:前两篇持久化分别讲到了 NSUserDefaults保存Settings信息 Plist保存简单的结构化信息 本文讲解如何保存需要加密的信息.绝大多数情况下都是保存密码.少数情况下需要保存证书等信息.本文以密码为例,讲解如何用iOS SDK原生API来进行KeyChain的操作. 实际开发的过程中,建议使用一些Github的集成库,或者自己写一个KeyChain的库,很简单 源代码提供

python多线程之线程锁三(同一时间允许多个线程)

#coding:utf-8 import threading import time num = 0 #全局变量 def runs():     time.sleep(1)     global num #在函数内部要对全局变量进行更改,需要进行声明     samp.acquire() #在操作时,获得锁,4个线程都在里边被锁住     time.sleep(0.001)     num += 1    #虽然4个线程同时对num进行加1,但在相加的时候,CPU还是一个一个的加     pri

pthread_mutex_lock线程锁使用简单示例

#define __USE_LARGEFILE64 #define _LARGEFILE64_SOURCE #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <sys/types.h> #include <fcntl.h> #include <unistd.h

iOS 多线程 NSThread NSOperation NSOperationQueue GCD 线程锁 线程阻塞

iPhone中的线程应用并不是无节制的,官方给出的资料显示,iPhone OS下的主线程的堆栈大小是1M,第二个线程开始就是512KB,并且该值不能通过编译器开关或线程API函数来更改,只有主线程有直接修改UI的能力,所以一些数据层面可以开辟线程来操作进行,iOS线程的操作方法有NSThread NSOperation NSOperationQueue GCD: NSThread方法有 //NSThread自动 - (IBAction)didClickNSThreadAutoButtonActi