线程锁是什么
在前面的文章中总结过多线程,总结了多线程之后,线程锁也是必须要好好总结的东西,这篇文章构思的时候可能写的东西得许多,只能挤时间一点点的慢慢的总结了,知道了线程之后要了解线程锁就得先了解一下什么是“线程锁”。
“线程锁”一段代码在同一个时间内是只能被一个线程访问的,为了避免在同一时间内有多个线程访问同一段代码就有了“锁”的概念,比如说,线程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它的一些新的特性,不然很久不写,一些东西比较容易遗忘!