zoukankan      html  css  js  c++  java
  • iOS 加锁的方式

    iOS多线程编程中,经常碰到多个线程访问共同的一个资源,在线程相互交互的情况下,需要一些同步措施,来保证线程之间交互的时候是安全的。下面我们一起看一下学一下iOS的几种常用的加锁方式,希望对大家有所帮助!!!

    1. @synchronized
    2. NSLock对象锁
    3. NSRecursiveLock递归锁
    4. NSConditionLock条件锁
    5. dispatch_semaphore 信号量实现加锁(也就是GCD)
    6. OSSpinLock 与 os_unfair_lock
    7. pthread_mutex

    介绍与使用

    1.@synchronized

    @synchronized关键字加锁,互斥锁,性能较差不推荐在项目中使用。

    @synchronized(这里添加一个OC对象,一般使用self) {
           这里写要加锁的代码
      }
    注意点
    1.加锁的代码要尽量少 2.添加的OC对象必须在多个线程中都是同一个对象 3.它的优点是不需要显式的创建锁对象,便可以实现锁的机制。 4. @synchronized块会隐式的添加异常处理例程来保护代码,该处理例程会在异常抛出的时候就会自动  的释放互斥锁。如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。

    下面我们以一个最经典的例子:卖票

    //设置票的数量为5
        _tickets = 5;
        
        //线程1
        dispatch_async(self.concurrentQueue, ^{
            [self saleTickets];
        });
        
        //线程2
        dispatch_async(self.concurrentQueue, ^{
            [self saleTickets];
        });
     
    - (void)saleTickets
    {
        while (1) {
            @synchronized(self) {
                [NSThread sleepForTimeInterval:1];
                if (_tickets > 0) {
                    _tickets--;
                    NSLog(@"剩余票数= %ld, Thread:%@",_tickets,[NSThread currentThread]);
                } else {
                    NSLog(@"票卖完了  Thread:%@",[NSThread currentThread]);
                    break;
                }
            }
        }
    }

    2.NSLock

    基本所有锁的接口都是通过NSLocking协议定义的,定义了lock和unlock方法,通过这些方法获取和释放锁。NSLock是对mutex普通锁的封装

    下面还是以卖票的例子讲述一下。

    //设置票的数量为5
        _tickets = 5;
        
        //创建锁
        _mutexLock = [[NSLock alloc] init];
        
        //线程1
        dispatch_async(self.concurrentQueue, ^{
            [self saleTickets];
        });
        
        //线程2
        dispatch_async(self.concurrentQueue, ^{
            [self saleTickets];
        });
     
    - (void)saleTickets
    {
     
        while (1) {
            [NSThread sleepForTimeInterval:1];
            //加锁
            [_mutexLock lock];
            if (_tickets > 0) {
                _tickets--;
                NSLog(@"剩余票数= %ld, Thread:%@",_tickets,[NSThread currentThread]);        
            } else {
                NSLog(@"票卖完了  Thread:%@",[NSThread currentThread]);
                break;
            }
            //解锁
            [_mutexLock unlock];
        }
    }

    3.NSRecursiveLock递归锁

    使用锁比较容易犯的错误是在递归或者循环中造成死锁。

    如下代码锁会被多次lock,造成自己被阻塞。

    //创建锁
        _mutexLock = [[NSLock alloc]init];
      
        //线程1
        dispatch_async(self.concurrentQueue, ^{
            static void(^TestMethod)(int);
            TestMethod = ^(int value)
            {
                [_mutexLock lock];
                if (value > 0)
                {
                    [NSThread sleepForTimeInterval:1];
                    TestMethod(value--);
                }
                [_mutexLock unlock];
            };
            
            TestMethod(5);
        });

    如果把这个NSLock换成NSRecursiveLock,就可以解决问题。

    NSRecursiveLock类定义的锁,可以在同一线程多次lock,不会造成死锁。

    //创建锁
        _rsLock = [[NSRecursiveLock alloc] init];
        
       //线程1
        dispatch_async(self.concurrentQueue, ^{
            static void(^TestMethod)(int);
            TestMethod = ^(int value)
            {
                [_rsLock lock];
                if (value > 0)
                {
                    [NSThread sleepForTimeInterval:1];
                    TestMethod(value--);
                }
                [_rsLock unlock];
            };
            
            TestMethod(5);
        });

    4.NSConditionLock条件锁

    NSMutableArray *products = [NSMutableArray array];  
    NSInteger HAS_DATA = 1;  
    NSInteger NO_DATA = 0;  
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
        while (1) {  
            [lock lockWhenCondition:NO_DATA];  
            [products addObject:[[NSObject alloc] init]];  
            NSLog(@"produce a product,总量:%zi",products.count);  
            [lock unlockWithCondition:HAS_DATA];  
            sleep(1);  
        }  
    });  
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
        while (1) {  
           NSLog(@"wait for product");  
            [lock lockWhenCondition:HAS_DATA];  
           [products removeObjectAtIndex:0];  
           NSLog(@"custome a product");  
           [lock unlockWithCondition:NO_DATA];  
        }  
    });

    在线程1中的加锁使用了lock,所以是不要条件的,也就锁住了。但在unlock的使用整型条件,它可以开启其他线程中正在等待钥匙的临界池,当线程1循环到一次的时候,打开了线程2的阻塞。

    NSCoditionLock中lock,lockWhenCondition:与unlock,unlockWithCondition:是可以随意组合的,具体使用根据需求来区分。

    NSCoditionLock 是对NSCodition的进一步封装,可以设置具体的条件值,而NSCodition是对mutex和cond的封装---看本篇博客7.3 条件锁

    5.dispatch_semaphore信号量实现加锁

    dispatch_semaphore_t signal = dispatch_semaphore_create(1);  
    dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);  
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
        dispatch_semaphore_wait(signal, overTime);  
                NSLog(@"需要线程同步的操作1 开始");  
                sleep(2);  
                NSLog(@"需要线程同步的操作1 结束");  
            dispatch_semaphore_signal(signal);  
    });  
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
            sleep(1);  
            dispatch_semaphore_wait(signal, overTime);  
                NSLog(@"需要线程同步的操作2");  
            dispatch_semaphore_signal(signal);  
    });  

    dispatch_semaphore是GCD用于同步的方式,与之相关的共有三个函数,dispatch_semaphore_wait,dispatch_semaphore_signal,dispatch_semaphore_create。

    (1)dispatch_semaphore_create的声明为:

    dispatch_semaphore_t dispatch_semaphore_create(long value);

    传入的参数是long类型,输出一个dispatch_semaphore_t类型值为Value的信号量(value传入值不能小于0,否则会报错NULL)

    (2)dispatch_semaphore_signal声明为下面:

    long dispatch_semaphore_signal(dispatch_semaphore_t dsema); 

    这个方法会使dsema加1;

    (3)dispatch_semaphore_wait的声明为下面:

    long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

    这个方法会使dsema减1。

    整个逻辑如下:

    如果dsema信号量值为大于0,该函数所在线程就会继续执行下面的语句,并将信号量的减去1;如果dsema为0时,函数就会阻塞当前的线程,如果等待的期间发现dsema的值被dispatch_semaphore_signal加1了,并且该函数得到了信号量,那么继续向下执行,并将信号量减1,如果等待期间没有获得信号量或者值一直为0,那么等到timeout,所处的线程也会自动执行下面的代码。

    dispatch_semaphore,当信号量为1时,可以作为锁使用。如果没有出现等待的情况,它的性能比pthread_mutex还要高,当如果有等待情况的时候,性能就会下降很多,相比OSSpinLock(暂不讲解),它的优势在于等待的时侯不会消耗CPU资源。

    针对上面代码,发现如果超时时间overTime>2,可完成同步操作,反之,在线程1还没有执行完的情况下,此时超时了,将自动执行下面的代码。

    上面代码执行结果:

    2018-09-18 15:40:52.324 SafeMultiThread[35945:579032] 需要线程同步的操作1 开始  
    2018-09-18 15:40:52.325 SafeMultiThread[35945:579032] 需要线程同步的操作1 结束  
    2018-09-18 15:40:52.326 SafeMultiThread[35945:579033] 需要线程同步的操作2  

    如果将overTime<2s的时候,执行为

    2018-09-18 15:40:52.049 SafeMultiThread[30834:434334] 需要线程同步的操作1 开始  
    2018-09-18 15:40:52.554 SafeMultiThread[30834:434332] 需要线程同步的操作2  
    2018-09-18 15:40:52.054 SafeMultiThread[30834:434334] 需要线程同步的操作1 结束  

    6.OSSpinLock自旋锁与os_unfair_lock

    6.1   OSSpinLock

    OSSpinLock叫做“自旋锁”,自旋锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源

    但是目前是不再安全了,在iOS 10之后弃用啦,如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁。

    在使用的过程中需要导入头文件#import <libkern/OSAtomic.h>

    //初始化
    OSSpinLock lock = OS_SPINLOCK_INIT;
    //尝试加锁(如果需要等待就不加锁,直接返回false;如果不需要等待就加锁,返回True)
    bool result = OSSpinLockTry(&lock);
    //加锁
    OSSpinLockLock(&lock);
    //解锁
    OSSpinLockUnlock(&lock)

    6.2 os_unfair_lock互斥锁

    os_unfair_lock用于取代不安全的OSSpinLock,从iOS10开始才支持。从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等

    os_unfair_lock需要导入头文件#import<os/lock.h>

        //初始化
        os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
        //尝试加锁
        os_unfair_lock_trylock(&lock);
        //加锁
        os_unfair_lock_lock(&lock);
        //解锁
        os_unfair_lock_unlock(&lock);

    7.pthread_mutex

    mutex叫做“互斥锁”,等待锁的线程处于休眠状态

    需要导入头文件#import <pthread.h>

    7.1  

        //初始化锁的属性
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
        
        //初始化锁
        pthread_mutex_t mutex;
        pthread_mutex_init(&mutex, &attr);
        
        //尝试加锁
        pthread_mutex_trylock(&mutex);
        
        //加锁
        pthread_mutex_lock(&mutex);
        
        //解锁
        pthread_mutex_unlock(&mutex);
        
        //销毁相关资源 --pthread_mutex在对象类释放的时候要销毁,其他锁无此情况
        pthread_mutexattr_destroy(&attr);
        pthread_mutex_destroy(&mutex);

    7.2 pthread_mutex-递归锁

         //初始化锁的属性
        pthread_mutexattr_t attr;
        pthread_mutexattr_t_int(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);//递归锁
        
        //初始化锁
        pthread_mutex_t mutex;
        pthread_mutex_init(&mutex, &attr);

    7.3 pthread_mutex-条件

    #import "MutexDemo3.h"
    
    #import <pthread.h>
    
    @interface MutexDemo3()
    @property (assign, nonatomic) pthread_mutex_t mutex;
    @property (assign, nonatomic) pthread_cond_t cond;
    @property (strong, nonatomic) NSMutableArray *data;
    @end
    
    @implementation MutexDemo3
    
    - (instancetype)init
    {
        if (self = [super init]) {
            // 初始化属性
            pthread_mutexattr_t attr;
            pthread_mutexattr_init(&attr);
            pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
            // 初始化锁
            pthread_mutex_init(&_mutex, &attr);
            // 销毁属性
            pthread_mutexattr_destroy(&attr);
            
            // 初始化条件
            pthread_cond_init(&_cond, NULL);
            
            self.data = [NSMutableArray array];
        }
        return self;
    }
    
    - (void)otherTest
    {
        [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
        
        [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
    }
    
    // 生产者-消费者模式
    
    // 线程1
    // 删除数组中的元素
    - (void)__remove
    {
        pthread_mutex_lock(&_mutex);
        NSLog(@"__remove - begin");
        
        if (self.data.count == 0) {
            // 等待
            pthread_cond_wait(&_cond, &_mutex);
        }
        
        [self.data removeLastObject];
        NSLog(@"删除了元素");
        
        pthread_mutex_unlock(&_mutex);
    }
    
    // 线程2
    // 往数组中添加元素
    - (void)__add
    {
        pthread_mutex_lock(&_mutex);
        
        sleep(1);
        
        [self.data addObject:@"Test"];
        NSLog(@"添加了元素");
        
        // 信号
        pthread_cond_signal(&_cond);
        // 广播
    //    pthread_cond_broadcast(&_cond);
        
        pthread_mutex_unlock(&_mutex);
    }
    
    - (void)dealloc
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }
    
    @end

    补充:自旋锁、互斥锁比较

    1. 什么情况下使用自旋锁比较划算?(OSSpinLock,但被os_unfair_lock取代,但是os_unfair_lock不是自旋锁)

    • 预计线程等待锁的时间很短
    • 加锁的代码(临界区)经常被调用,但竞争情况很少发生
    • CPU资源不紧张
    • 多核处理器

    2. 什么情况下使用互斥锁比较划算?(pthread_mutex, NSLock等)

    • 预计线程等待锁的时间较长
    • 单核处理器
    • 临界区有IO操作
    • 临界区代码复杂或者循环量大
    • 临界区竞争非常激烈

    以上就是自己在开发中所经常使用到的加锁方式,希望对大家有所帮助!!!

  • 相关阅读:
    ckeditor+粘贴word
    java+批量下载大文件
    链接批量下载文件
    java+大文件上传解决方案
    php+提高大文件上传速度
    富文本粘贴word文档内容图片处理
    软件开发工具(三)——理论与开发过程
    c结构体里的数组与指针
    Java解析Property文件
    怎样高速编译mediatekoperator以下代码
  • 原文地址:https://www.cnblogs.com/guohai-stronger/p/9663459.html
Copyright © 2011-2022 走看看