线程安全与锁

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: 1
2017-11-28 23:34:11.551619+0800 BasicDemo[75679:5312248] total: 3
2017-11-28 23:34:11.551618+0800 BasicDemo[75679:5312249] total: 2
2017-11-28 23:34:11.552120+0800 BasicDemo[75679:5312246] total: 2
2017-11-28 23:34:11.552143+0800 BasicDemo[75679:5312248] total: 1
2017-11-28 23:34:11.552171+0800 BasicDemo[75679:5312249] total: 0
//第二次输出
2017-11-28 23:34:55.738947+0800 BasicDemo[75683:5313401] total: 1
2017-11-28 23:34:55.738979+0800 BasicDemo[75683:5313403] total: 2
2017-11-28 23:34:55.738985+0800 BasicDemo[75683:5313402] total: 3
2017-11-28 23:34:55.739565+0800 BasicDemo[75683:5313401] total: 2
2017-11-28 23:34:55.739570+0800 BasicDemo[75683:5313402] total: 1
2017-11-28 23:34:55.739577+0800 BasicDemo[75683:5313403] total: 0
NSInteger 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: 1
2017-11-28 23:35:37.696928+0800 BasicDemo[75696:5314483] total: 0
2017-11-28 23:35:37.696971+0800 BasicDemo[75696:5314481] total: 1
2017-11-28 23:35:37.696995+0800 BasicDemo[75696:5314481] total: 0
2017-11-28 23:35:37.697026+0800 BasicDemo[75696:5314482] total: 1
2017-11-28 23:35:37.697050+0800 BasicDemo[75696:5314482] total: 0
//第二次输出
2017-11-28 23:36:01.790264+0800 BasicDemo[75700:5315159] total: 1
2017-11-28 23:36:01.790617+0800 BasicDemo[75700:5315159] total: 0
2017-11-28 23:36:01.790668+0800 BasicDemo[75700:5315161] total: 1
2017-11-28 23:36:01.790687+0800 BasicDemo[75700:5315161] total: 0
2017-11-28 23:36:01.790711+0800 BasicDemo[75700:5315160] total: 1
2017-11-28 23:36:01.790735+0800 BasicDemo[75700:5315160] total: 0

从上面能够看出,第一个函数第一次和第二次调用的结果不同,换句话说,不能肯定代码的运行顺序和结果,是线程不安全的;第二个函数第一次和第二次输出结果同样,能够肯定函数的执行结果,是线程安全的。html

线程不安全是因为多线程访问形成的,那么如何解决?算法

 

1.既然线程安全问题是由多线程引发的,那么,最极端的可使用单线程保证线程安全。api

2.线程安全是因为多线程访问和修改共享资源而引发不可预测的结果,所以,若是都是访问共享资源而不去修改共享资源也能够保证线程安全,好比:设置只读属性的全局变量。数组

3.使用锁。缓存

  锁是最经常使用的同步工具。一段代码段在同一个时间只能容许被一个线程访问,好比一个线程A进入加锁代码以后因为已经加锁,另外一个线程B就没法访问,只有等待前一个线程A执行完加锁代码后解锁,B线程才能访问加锁代码。不要将过多的其余操做代码放到里面,不然一个线程执行的时候另外一个线程就一直在等待,就没法发挥多线程的做用了。安全

 
NSLock
在Cocoa程序中NSLock中实现了一个简单的互斥锁,实现了NSLocking protocol。
lock,加锁
unlock,解锁
tryLock,尝试加锁,若是失败了,并不会阻塞线程,只是当即返回
NOlockBeforeDate:,在指定的date以前暂时阻塞线程(若是没有获取锁的话),若是到期尚未获取锁,则线程被唤醒,函数当即返回NO
使用tryLock并不能成功加锁,若是获取锁失败就不会执行加锁代码了。
- (void)getIamgeName:(NSMutableArray *)imageNames{
    NSString *imageName;
    [lock lock];
    if (imageNames.count>0) {
        imageName = [imageNames lastObject];
        [imageNames removeObject:imageName];
    }
    [lock unlock];
}

@synchronized代码块

每一个iOS开发最先接触的线程锁就是@synchronized,代码简单。数据结构

- (void)getIamgeName:(int)index{
    NSString *imageName;
    @synchronized(self) {
        if (imageNames.count>0) {
            imageName = [imageNames lastObject];
            [imageNames removeObject:imageName];
        }
    }
}

条件信号量dispatch_semaphore_t

dispatch_semaphore_tGCD中信号量,也能够解决资源抢占问题,支持信号通知和信号等待。每当发送一个信号通知,则信号量+1;每当发送一个等待信号时信号量-1,;若是信号量为0则信号会处于等待状态,直到信号量大于0开始执行。多线程

#import "DispatchSemaphoreViewController.h"

@interface DispatchSemaphoreViewController ()
{
    dispatch_semaphore_t semaphore;
}
@end

@implementation DispatchSemaphoreViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    semaphore = dispatch_semaphore_create(1);
    /**
     *  建立一个信号量为1的信号
     *
     */
}

- (void)getIamgeName:(NSMutableArray *)imageNames{
    NSString *imageName;
    /**
     *  semaphore:等待信号
     DISPATCH_TIME_FOREVER:等待时间
     wait以后信号量-1,为0
     */
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    if (imageNames.count>0) {
        imageName = [imageNames lastObject];
        [imageNames removeObject:imageName];
    }
    /**
     *  发送一个信号通知,这时候信号量+1,为1
     */
    dispatch_semaphore_signal(semaphore);
}

@end

 

条件锁NSCondition

NSCondition一样实现了NSLocking协议,因此它和NSLock同样,也有NSLocking协议的lock和unlock方法,能够当作NSLock来使用解决线程同步问题,用法彻底同样。并发

- (void)getIamgeName:(NSMutableArray *)imageNames{
    NSString *imageName;
    [lock lock];
    if (imageNames.count>0) {
        imageName = [imageNames lastObject];
        [imageNames removeObject:imageName];
    }
    [lock unlock];
}
  同时,NSCondition提供更高级的用法。wait和signal,和条件信号量相似。
  好比咱们要监听imageNames数组的个数,当imageNames的个数大于0的时候就执行清空操做。思路是这样的,当imageNames个数大于0时执行清空操做,不然,wait等待执行清空操做。当imageNames个数增长的时候发生signal信号,让等待的线程唤醒继续执行。
  NSCondition和NSLock、@synchronized等是不一样的是,NSCondition能够给每一个线程分别加锁,加锁后不影响其余线程进入临界区。这是很是强大。
  可是正是由于这种分别加锁的方式,NSCondition使用wait并使用加锁后并不能真正的解决资源的竞争。好比咱们有个需求:不能让m<0。假设当前m=0,线程A要判断到m>0为假,执行等待;线程B执行了m=1操做,并唤醒线程A执行m-1操做的同时线程C判断到m>0,由于他们在不一样的线程锁里面,一样判断为真也执行了m-1,这个时候线程A和线程C都会执行m-1,可是m=1,结果就会形成m=-1.
当我用数组作删除试验时,作增删操做并非每次都会出现,大概3-4次后会出现。单纯的使用lock、unlock是没有问题的。
- (void)getIamgeName:(NSMutableArray *)imageNames{
    NSString *imageName;
    [lock lock];    //加锁
    static int m = 0;
    static int n = 0;
    static int p = 0;
    NSLog(@"removeObjectBegin count: %ld\n",imageNames.count);
    
    if (imageNames.count>0) {
        imageName = [imageNames lastObject];
        [imageNames removeObjectAtIndex:0];
        m++;
        NSLog(@"执行了%d次删除操做",m);
    } else {
        p++;
        NSLog(@"执行了%d次等待",p);
        [lock wait];    //等待
        imageName = [imageNames lastObject];
        [imageNames removeObjectAtIndex:0];
        /**
         *  有时候点击取出图片会崩溃
         */
        n++;
        NSLog(@"执行了%d次继续操做",n);
    }
    
    NSLog(@"removeObject count: %ld\n",imageNames.count);
    [lock unlock];     //解锁
}
- (void)createImageName:(NSMutableArray *)imageNames{
    [lock lock];
    static int m = 0;
    [imageNames addObject:@"0"];
    m++;
    NSLog(@"添加了%d次",m);
    [lock signal];  //唤醒随机一个线程取消等待继续执行
    
//        [lock broadcast];   //唤醒全部线程取消等待继续执行
    NSLog(@"createImageName count: %ld\n",imageNames.count);
    [lock unlock];
}

#pragma mark - 多线程取出图片后删除
- (void)getImageNameWithMultiThread{
    [lock broadcast];
    NSMutableArray *imageNames = [[NSMutableArray alloc]init];
    dispatch_group_t dispatchGroup = dispatch_group_create();
    __block double then, now;
    then = CFAbsoluteTimeGetCurrent();
    for (int i=0; i<10; i++) {
        dispatch_group_async(dispatchGroup, self.synchronizationQueue, ^(){
            [self getIamgeName:imageNames];
        });
        dispatch_group_async(dispatchGroup, self.synchronizationQueue, ^(){
            [self createImageName:imageNames];
        });
    }
    dispatch_group_notify(dispatchGroup, self.synchronizationQueue, ^(){
        now = CFAbsoluteTimeGetCurrent();
        printf("thread_lock: %f sec\nimageNames count: %ld\n", now-then,imageNames.count);
    });
    
}

条件锁NSConditionLock

也有人说这是个互斥锁
NSConditionLock一样实现了NSLocking协议,试验过程当中发现性能很低。async

- (void)getIamgeName:(NSMutableArray *)imageNames{
    NSString *imageName;
    [lock lock];
    if (imageNames.count>0) {
        imageName = [imageNames lastObject];
        [imageNames removeObject:imageName];
    }
    [lock unlock];
}

NSConditionLock也能够像NSCondition同样作多线程之间的任务等待调用,并且是线程安全的。

- (void)getIamgeName:(NSMutableArray *)imageNames{
    NSString *imageName;
    [lock lockWhenCondition:1];    //加锁
    if (imageNames.count>0) {
        imageName = [imageNames lastObject];
        [imageNames removeObjectAtIndex:0];
    }
    [lock unlockWithCondition:0];     //解锁
}
- (void)createImageName:(NSMutableArray *)imageNames{
    [lock lockWhenCondition:0];
    [imageNames addObject:@"0"];
    [lock unlockWithCondition:1];
}

#pragma mark - 多线程取出图片后删除
- (void)getImageNameWithMultiThread{
    NSMutableArray *imageNames = [[NSMutableArray alloc]init];
    dispatch_group_t dispatchGroup = dispatch_group_create();
    __block double then, now;
    then = CFAbsoluteTimeGetCurrent();
    for (int i=0; i<10000; i++) {
        dispatch_group_async(dispatchGroup, self.synchronizationQueue, ^(){
            [self getIamgeName:imageNames];
        });
        dispatch_group_async(dispatchGroup, self.synchronizationQueue, ^(){
            [self createImageName:imageNames];
        });
    }
    dispatch_group_notify(dispatchGroup, self.synchronizationQueue, ^(){
        now = CFAbsoluteTimeGetCurrent();
        printf("thread_lock: %f sec\nimageNames count: %ld\n", now-then,imageNames.count);
    });
}

递归锁NSRecursiveLock

  有时候“加锁代码”中存在递归调用,递归开始前加锁,递归调用开始后会重复执行此方法以致于反复执行加锁代码最终形成死锁,这个时候可使用递归锁来解决。使用递归锁能够在一个线程中反复获取锁而不形成死锁,这个过程当中会记录获取锁和释放锁的次数,只有最后二者平衡锁才被最终释放。

- (void)getIamgeName:(NSMutableArray *)imageNames{
    NSString *imageName;
    [lock lock];
    if (imageNames.count>0) {
        imageName = [imageNames firstObject];
        [imageNames removeObjectAtIndex:0];
        [self getIamgeName:imageNames];
    }
    [lock unlock];
}
- (void)getImageNameWithMultiThread{
    NSMutableArray *imageNames = [NSMutableArray new];
    int count = 1024*10;
    for (int i=0; i<count; i++) {
        [imageNames addObject:[NSString stringWithFormat:@"%d",i]];
    }
    dispatch_group_t dispatchGroup = dispatch_group_create();
    __block double then, now;
    then = CFAbsoluteTimeGetCurrent();
    dispatch_group_async(dispatchGroup, self.synchronizationQueue, ^(){
        [self getIamgeName:imageNames];
    });
    dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
        now = CFAbsoluteTimeGetCurrent();
        printf("thread_lock: %f sec\nimageNames count: %ld\n", now-then,imageNames.count);
    });
    
}

NSDistributedLock

  NSDistributedLock是MAC开发中的跨进程的分布式锁,底层是用文件系统实现的互斥锁。NSDistributedLock没有实现NSLocking协议,因此没有lock方法,取而代之的是非阻塞的tryLock方法。

NSDistributedLock *lock = [[NSDistributedLock alloc] initWithPath:@"/Users/mac/Desktop/lock.lock"];
    while (![lock tryLock])
    {
        sleep(1);
    }
    
    //do something
    [lock unlock];
当执行到do something时程序退出,程序再次启动以后tryLock就不再能成功了,陷入死锁状态.其余应用也不能访问受保护的共享资源。在这种状况下,你可使用breadLock方法来打破现存的锁以便你能够获取它。可是一般应该避免打破锁,除非你肯定拥有进程已经死亡并不可能再释放该锁。

互斥锁POSIX

POSIX和dispatch_semaphore_t很像,可是彻底不一样。POSIX是Unix/Linux平台上提供的一套条件互斥锁的API。
新建一个简单的POSIX互斥锁,引入头文件 #import <pthread.h>声明并初始化一个pthread_mutex_t的结构。使用pthread_mutex_lock和pthread_mutex_unlock函数。调用pthread_mutex_destroy来释放该锁的数据结构。
#import <pthread.h>
@interface MYPOSIXViewController ()
{
    pthread_mutex_t mutex;  //声明pthread_mutex_t的结构
}
@end

@implementation MYPOSIXViewController
- (void)dealloc{
    pthread_mutex_destroy(&mutex);  //释放该锁的数据结构
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    pthread_mutex_init(&mutex, NULL);
    /**
     *  初始化
     *
     */
}

- (void)getIamgeName:(NSMutableArray *)imageNames{
    NSString *imageName;
    /**
     *  加锁
     */
    pthread_mutex_lock(&mutex);
    if (imageNames.count>0) {
        imageName = [imageNames firstObject];
        [imageNames removeObjectAtIndex:0];
    }
    /**
     *  解锁
     */
    pthread_mutex_unlock(&mutex);
}

POSIX还能够建立条件锁,提供了和NSCondition同样的条件控制,初始化互斥锁同时使用pthread_cond_init来初始化条件数据结构,

// 初始化
    int pthread_cond_init (pthread_cond_t *cond, pthread_condattr_t *attr);
    
    // 等待(会阻塞)
    int pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mut);
    
    // 定时等待
    int pthread_cond_timedwait (pthread_cond_t *cond, pthread_mutex_t *mut, const struct timespec *abstime);
    
    // 唤醒
    int pthread_cond_signal (pthread_cond_t *cond);
    
    // 广播唤醒
    int pthread_cond_broadcast (pthread_cond_t *cond);
    
    // 销毁
    int pthread_cond_destroy (pthread_cond_t *cond);
OSIX还提供了不少函数,有一套完整的API,包含Pthreads线程的建立控制等等,很是底层,能够手动处理线程的各个状态的转换即管理生命周期,甚至能够实现一套本身的多线程,感兴趣的能够继续深刻了解。推荐一篇详细文章,但不是基于iOS的,是基于Linux的,可是介绍的很是详细 Linux 线程锁详解
 

自旋锁OSSpinLock

首先要提的是OSSpinLock已经出现了BUG,致使并不能彻底保证是线程安全的。

新版 iOS 中,系统维护了 5 个不一样的线程优先级/QoS: background,utility,default,user-initiated,user-interactive。高优先级线程始终会在低优先级线程前执行,一个线程不会受到比它更低优先级线程的干扰。这种线程调度算法会产生潜在的优先级反转问题,从而破坏了 spin lock。
具体来讲,若是一个低优先级的线程得到锁并访问共享资源,这时一个高优先级的线程也尝试得到这个锁,它会处于 spin lock 的忙等状态从而占用大量 CPU。此时低优先级线程没法与高优先级线程争夺 CPU 时间,从而致使任务迟迟完不成、没法释放 lock。这并不仅是理论上的问题,libobjc 已经遇到了不少次这个问题了,因而苹果的工程师停用了 OSSpinLock。
苹果工程师 Greg Parker 提到,对于这个问题,一种解决方案是用 truly unbounded backoff 算法,这能避免 livelock 问题,但若是系统负载高时,它仍有可能将高优先级的线程阻塞数十秒之久;另外一种方案是使用 handoff lock 算法,这也是 libobjc 目前正在使用的。锁的持有者会把线程 ID 保存到锁内部,锁的等待者会临时贡献出它的优先级来避免优先级反转的问题。理论上这种模式会在比较复杂的多锁条件下产生问题,但实践上目前还一切都好。
OSSpinLock 自旋锁,性能最高的锁。原理很简单,就是一直 do while 忙等。它的缺点是当等待时会消耗大量 CPU 资源,因此它不适用于较长时间的任务。对于内存缓存的存取来讲,它很是合适。
因此说不建议再继续使用,不过能够拿来玩耍一下,导入头文件 #import <libkern/OSAtomic.h>
#import <libkern/OSAtomic.h>
@interface MYOSSpinLockViewController ()
{
    OSSpinLock spinlock;  //声明pthread_mutex_t的结构
}
@end

@implementation MYOSSpinLockViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    spinlock = OS_SPINLOCK_INIT;
    /**
     *  初始化
     *
     */
}

- (void)getIamgeName:(NSMutableArray *)imageNames{
    NSString *imageName;
    /**
     *  加锁
     */
    OSSpinLockLock(&spinlock);
    if (imageNames.count>0) {
        imageName = [imageNames firstObject];
        [imageNames removeObjectAtIndex:0];
    }
    /**
     *  解锁
     */
    OSSpinLockUnlock(&spinlock);
}
@end

 

GCD线程阻断dispatch_barrier_async/dispatch_barrier_sync

dispatch_barrier_async/dispatch_barrier_sync在必定的基础上也能够作线程同步,会在线程队列中打断其余线程执行当前任务,也就是说只有用在并发的线程队列中才会有效,由于串行队列原本就是一个一个的执行的,你打断执行一个和插入一个是同样的效果。两个的区别是是否等待任务执行完成。

注意:若是在当前线程调用dispatch_barrier_sync打断会发生死锁。

@interface MYdispatch_barrier_syncViewController ()
{
        __block double then, now;
}
@end

@implementation MYdispatch_barrier_syncViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}
- (void)getIamgeName:(NSMutableArray *)imageNames{
    NSString *imageName;
    if (imageNames.count>0) {
        imageName = [imageNames firstObject];
        [imageNames removeObjectAtIndex:0];
    }else{
        now = CFAbsoluteTimeGetCurrent();
        printf("thread_lock: %f sec\nimageNames count: %ld\n", now-then,imageNames.count);
    }
}

- (void)getImageNameWithMultiThread{
    NSMutableArray *imageNames = [NSMutableArray new];
    int count = 1024*11;
    for (int i=0; i<count; i++) {
        [imageNames addObject:[NSString stringWithFormat:@"%d",i]];
    }
    then = CFAbsoluteTimeGetCurrent();
    for (int i=0; i<count+1; i++) {
        //100来测试锁有没有正确的执行
        dispatch_barrier_async(self.synchronizationQueue, ^{
             [self getIamgeName:imageNames];
        });
    }
}

总结

@synchronized:适用线程很少,任务量不大的多线程加锁 NSLock:其实NSLock并无想象中的那么差,不知道你们为何不推荐使用 dispatch_semaphore_t:使用信号来作加锁,性能提高显著 NSCondition:使用其作多线程之间的通讯调用不是线程安全的 NSConditionLock:单纯加锁性能很是低,比NSLock低不少,可是能够用来作多线程处理不一样任务的通讯调用 NSRecursiveLock:递归锁的性能出奇的高,可是只能做为递归使用,因此限制了使用场景 NSDistributedLock:由于是MAC开发的,就不讨论了 POSIX(pthread_mutex):底层的api,复杂的多线程处理建议使用,而且能够封装本身的多线程 OSSpinLock:性能也很是高,惋惜出现了线程问题 dispatch_barrier_async/dispatch_barrier_sync:测试中发现dispatch_barrier_sync比dispatch_barrier_async性能要高,真是大出意外

相关文章
相关标签/搜索