您的位置:首页 > 移动开发 > IOS开发

iOS的线程安全与锁

2017-12-30 00:00 267 查看
点击上方“程序员大咖”,选择“置顶公众号”关键时刻,第一时间送达!




在iOS编码中,锁的出现其实是因为多线程会出现线程安全的问题。那么,问题来了,什么是线程安全?为什么锁可以解决线程安全问题?单线程是不是绝对的线程安全?iOS编程有多少种锁?加解锁的效率如何?......
一、什么是线程安全?
WIKI: Thread-safe code only manipulates shared data structures in a manner that ensures that all threads behave properly and fulfil their design specifications without unintended interaction.
用人话来说:多线程操作共享数据不会出现想不到的结果就是线程安全的,否则,是线程不安全的。
举个例子:
NSInteger total = 0;- (void)threadNotSafe {    for (NSInteger index = 0; index < 3; index++) {        dispatch_async(dispatch_get_global_queue(0, 0), ^{            total += 1;            NSLog(@"total: %ld", total);            total -= 1;            NSLog(@"total: %ld", total);        });    }}//第一次输出:2017-11-28 23:34:11.551570+0800 BasicDemo[75679:5312246] total: 12017-11-28 23:34:11.551619+0800 BasicDemo[75679:5312248] total: 32017-11-28 23:34:11.551618+0800 BasicDemo[75679:5312249] total: 22017-11-28 23:34:11.552120+0800 BasicDemo[75679:5312246] total: 22017-11-28 23:34:11.552143+0800 BasicDemo[75679:5312248] total: 12017-11-28 23:34:11.552171+0800 BasicDemo[75679:5312249] total: 0//第二次输出2017-11-28 23:34:55.738947+0800 BasicDemo[75683:5313401] total: 12017-11-28 23:34:55.738979+0800 BasicDemo[75683:5313403] total: 22017-11-28 23:34:55.738985+0800 BasicDemo[75683:5313402] total: 32017-11-28 23:34:55.739565+0800 BasicDemo[75683:5313401] total: 22017-11-28 23:34:55.739570+0800 BasicDemo[75683:5313402] total: 12017-11-28 23:34:55.739577+0800 BasicDemo[75683:5313403] total: 0NSInteger total = 0;NSLock *lock = [NSLock new];- (void)threadSafe {    for (NSInteger index = 0; index < 3; index++) {        dispatch_async(dispatch_get_global_queue(0, 0), ^{            [lock lock];            total += 1;            NSLog(@"total: %ld", total);            total -= 1;            NSLog(@"total: %ld", total);            [lock unlock];        });    }}//第一次输出2017-11-28 23:35:37.696614+0800 BasicDemo[75696:5314483] total: 12017-11-28 23:35:37.696928+0800 BasicDemo[75696:5314483] total: 02017-11-28 23:35:37.696971+0800 BasicDemo[75696:5314481] total: 12017-11-28 23:35:37.696995+0800 BasicDemo[75696:5314481] total: 02017-11-28 23:35:37.697026+0800 BasicDemo[75696:5314482] total: 12017-11-28 23:35:37.697050+0800 BasicDemo[75696:5314482] total: 0//第二次输出2017-11-28 23:36:01.790264+0800 BasicDemo[75700:5315159] total: 12017-11-28 23:36:01.790617+0800 BasicDemo[75700:5315159] total: 02017-11-28 23:36:01.790668+0800 BasicDemo[75700:5315161] total: 12017-11-28 23:36:01.790687+0800 BasicDemo[75700:5315161] total: 02017-11-28 23:36:01.790711+0800 BasicDemo[75700:5315160] total: 12017-11-28 23:36:01.790735+0800 BasicDemo[75700:5315160] total: 0
第一个函数第一次和第二次调用的结果不一样,换句话说,不能确定代码的运行顺序和结果,是线程不安全的;第二个函数第一次和第二次输出结果一样,可以确定函数的执行结果,是线程安全的。
居于线程安全的含义,知道线程安全是相对于多线程而言的,单线程不会存在线程安全问题。因为,单线程代码的执行顺序是确定的,可以知道代码的执行结果。
二、锁锁锁



线程不安全是由于多线程访问造成的,那么如何解决?
1.既然线程安全问题是由多线程引起的,那么,最极端的可以使用单线程保证线程安全。
2.线程安全是由于多线程访问和修改共享资源而引起不可预测的结果,因此,如果都是访问共享资源而不去修改共享资源也可以保证线程安全,比如:设置只读属性的全局变量。
3.使用锁。
引用 ibireme 在《不再安全的 OSSpinLock:https://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/》中的一张图片说明加解锁的效率:



我也下载了 ibireme 在 GitHub 上面的Demo来跑过(环境 iPhone6 iOS11.1)。发现,不同的循环次数,结果都不一样,并没有得到和 ibireme 一样的结果。所以,上面的柱状图也只做一个定向分析,并不是很准确的结果。
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,否则会返回Nulllong 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。


来自: 一剑孤城
链接:http://www.jianshu.com/p/c94de311e226

程序员大咖整理发布,转载请联系作者获得授权


[b]

【点击成为Python大神】
[/b]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: