一、什么是线程安全?

多线程操作共享数据不会出现想不到的结果就是线程安全的,否则,是线程不安全的。比如:多个线程同时访问或读取同一共享数据,每个线程的读到的数据都是一样的,也就不存在线程不安全。如果多个线程对同一资源进行读写操作,那么每个线程读到的结果就是不可预料的,线程是不安全的。

        因此,线程安全,一定是对多线程而言的;单个线程,不存在线程安全问题。

二、多线程锁

        多线程安全的解决方法就是加锁。

        引用 ibireme 在《不再安全的 OSSpinLock》中的一张图片说明加解锁的效率:


         

ios 线程号 ios线程锁_iOS

     

下面会对这些锁的实现原理和用法做简单的总结和处理(详细的实现原理,可以看 bestswift 的这篇文章《深入理解 iOS 开发中的锁》):

OSSpinLock:

自旋锁的实现原理比较简单,就是死循环。当a线程获得锁以后,b线程想要获取锁就需要等待a线程释放锁。在没有获得锁的期间,b线程会一直处于忙等的状态。如果a线程在临界区的执行时间过长,则b线程会消耗大量的cpu时间,不太划算。所以,自旋锁用在临界区执行时间比较短的环境性能会很高。

自旋锁的代码实现:


#         import          OSSpinLock lock = OS_SPINLOCK_INIT;        


         OSSpinLockLock(&lock);        


         //需要执行的代码        


         OSSpinLockUnlock(&lock);        


         //OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock)        


         //苹果在OSSpinLock注释表示被废弃,改用不安全的锁替代



dispatch_semaphore:

dispatch_semaphore实现的原理和自旋锁有点不一样。首先会先将信号量减一,并判断是否大于等于0,如果是,则返回0,并继续执行后续代码,否则,使线程进入睡眠状态,让出cpu时间。直到信号量大于0或者超时,则线程会被重新唤醒执行后续操作。


dispatch_semaphore_t lock = dispatch_semaphore_create(         1         );             //传入的参数必须大于或者等于0,否则会返回Null        


         long wait = dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);             //wait = 0,则表示不需要等待,直接执行后续代码;wait != 0,则表示需要等待信号或者超时,才能继续执行后续代码。lock信号量减一,判断是否大于0,如果大于0则继续执行后续代码;lock信号量减一少于或者等于0,则等待信号量或者超时。        


         //需要执行的代码        


         long signal = dispatch_semaphore_signal(lock);             //signal = 0,则表示没有线程需要其处理的信号量,换句话说,没有需要唤醒的线程;signal != 0,则表示有一个或者多个线程需要唤醒,则唤醒一个线程。(如果线程有优先级,则唤醒优先级最高的线程,否则,随机唤醒一个线程。)


pthread_mutex:

pthread_mutex表示互斥锁,和信号量的实现原理类似,也是阻塞线程并进入睡眠,需要进行上下文切换。

pthread_mutexattr_t attr;        


         pthread_mutexattr_init(&attr);        


         pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);        


                  


         pthread_mutex_t lock;        


         pthread_mutex_init(&lock, &attr);             //设置属性        


                  


         pthread_mutex_lock(&lock);             //上锁        


         //需要执行的代码        


         pthread_mutex_unlock(&lock);             //解锁



NSLock:

NSLock在内部封装了一个 pthread_mutex,属性为 PTHREAD_MUTEX_ERRORCHECK。


NSLock *lock = [NSLock          new         ];        


         [lock lock];        


         //需要执行的代码        


         [lock unlock];



NSCondition:

NSCondition封装了一个互斥锁和条件变量。互斥锁保证线程安全,条件变量保证执行顺序。


NSCondition *lock = [NSCondition          new         ];        


         [lock lock];        


         //需要执行的代码        


         [lock unlock];



pthread_mutex(recursive):

pthread_mutex锁的一种,属于递归锁。一般一个线程只能申请一把锁,但是,如果是递归锁,则可以申请很多把锁,只要上锁和解锁的操作数量就不会报错。


pthread_mutexattr_t attr;        


         pthread_mutexattr_init(&attr);        


         pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);        


                  


         pthread_mutex_t lock;        


         pthread_mutex_init(&lock, &attr);             //设置属性        


                  


         pthread_mutex_lock(&lock);             //上锁        


         //需要执行的代码        


         pthread_mutex_unlock(&lock);             //解锁



NSRecursiveLock:

递归锁,pthread_mutex(recursive)的封装。


NSRecursiveLock *lock = [NSRecursiveLock          new         ];        


         [lock lock];        


         //需要执行的代码        


         [lock unlock];



NSConditionLock:

NSConditionLock借助 NSCondition 来实现,本质是生产者-消费者模型。


NSConditionLock *lock = [NSConditionLock          new         ];        


         [lock lock];        


         //需要执行的代码        


         [lock unlock];


@synchronized:

一个对象层面的锁,锁住了整个对象,底层使用了互斥递归锁来实现。


NSObject *object = [NSObject          new         ];        


         @synchronized(object) {        


                  //需要执行的代码        


         }


三、总结

这里只是一些简单的总结,更多深入的研究请自行 Google。

参考:

深入理解 iOS 开发中的锁

不再安全的 OSSpinLock

Threading Programming Guide

关于 @synchronized,这儿比你想知道的还要多

OS中保证线程安全的几种方式与性能对比

iOS 常见知识点(三):Lock

iOS多线程到底不安全在哪里?