码迷,mamicode.com
首页 > 移动开发 > 详细

iOS中常见的锁

时间:2017-12-13 20:35:31      阅读:233      评论:0      收藏:0      [点我收藏+]

标签:blog   .com   images   bsp   异常处理   解决问题   test   property   max   

多线程的安全隐患

一块资源可能会被多个线程共享,也就是说多个线程可能会访问同一块资源。

比如多个线程同时操作同一个对象,同一个变量。

当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。

比如一个买票问题:

#import "ViewController.h"

@interface ViewController ()
@property (assign, nonatomic) NSInteger maxCount;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

   _maxCount = 20; // 线程1 [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; // 线程2 [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; } - (void)run{ while (1) { if (_maxCount > 0) { // 暂停一段时间 [NSThread sleepForTimeInterval:0.002]; _maxCount--; NSLog(@"卖了一张票 - %ld - %@",_maxCount,[NSThread currentThread]); }else{ NSLog(@"票卖完了"); break; } } }

 输出结果:

技术分享图片

可以看到,当多个线程同时访问同一个数据的时候,很容易出现数据错乱,资源争夺的现象。

1. @synchronized(锁对象) { // 需要锁定的代码  } 来解决

互斥锁,使用的是线程同步的技术。加锁的代码需要尽量少,这个锁 ?? 对象需要保持在多个线程中都是同一个对象。

优点:是不需要显示的创建锁对象,就可以实现锁的机制。

缺点:会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁,会消耗系统资源。

- (void)run{
    while (1) {
        // 这里使用self,或者一个全局对象也行,每个对象里面都有一把锁
        @synchronized(self){
            if (_maxCount > 0) {
                // 暂停一段时间
                [NSThread sleepForTimeInterval:0.002];
                _maxCount--;
                NSLog(@"卖了一张票 - %ld - %@",_maxCount,[NSThread currentThread]);
            }else{
                NSLog(@"票卖完了");
                break;
            }
        }
    }
}

 输出结果:

技术分享图片

2. NSLock

在Cocoa框架中,NSLock实现了一个简单的互斥锁,所有锁(包括NSLock)的接口,实际上都是通过NSLocking协议定义的,它定义了 lock(加锁)和 unlock(解锁)方法。

不能多次调用lock方法,会造成死锁。

我们使用NSLock来解决上面的问题:

#import "ViewController.h"

@interface ViewController ()
@property (assign, nonatomic) NSInteger maxCount;
@property (strong, nonatomic) NSLock *lock; // 数据所
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    _maxCount = 20;
    
    _lock = [[NSLock alloc] init];
    
    // 线程1
    [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
    
    // 线程2
    [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
    
}


- (void)run{
    while (1) {
        // 加锁
        [_lock lock];
        if (_maxCount > 0) {
            // 暂停一段时间
            [NSThread sleepForTimeInterval:0.002];
            _maxCount--;
            NSLog(@"卖了一张票 - %ld - %@",_maxCount,[NSThread currentThread]);
        }else{
            NSLog(@"票卖完了");
            break;
        }
        // 释放锁
        [_lock unlock];
    }
}

 输出结果:

技术分享图片

3. NSRecursiveLock 递归锁

如果在循环中,使用锁,很容易造成死锁。如下代码,在递归block中,多次的调用lock方法,锁会被多次的lock,所以自己也被阻塞了。

_lock = [[NSLock alloc] init];
    
    //线程1
    dispatch_async(dispatch_queue_create(NULL, 0), ^{
        static void(^TestMethod)(int);
        TestMethod = ^(int value)
        {
            [_lock lock];
            if (value > 0)
            {
                [NSThread sleepForTimeInterval:1];
                NSLog(@"执行一次哦");
                TestMethod(value--);
            }
            NSLog(@"是否执行到这里");
            [_lock unlock];
        };
        
        TestMethod(5);
    });

 输出内容:

2017-12-12 11:39:50.253155+0800 NSLock[1353:158620] 执行一次哦

此处将NSLock 换成 NSRecursiveLock 便可解决问题:

 NSRecursiveLock类定义的锁可以在同一线程多次lock,而不会造成死锁。递归锁会跟踪它会被多少次lock,每次成功的lock都必须平衡调用unlock操作。只有所有的锁住和解锁操作都平衡的时候,锁才真正被释放给其它线程获得。

_lock = [[NSRecursiveLock alloc] init];
    
    //线程1
    dispatch_async(dispatch_queue_create(NULL, 0), ^{
        static void(^TestMethod)(int);
        TestMethod = ^(int value)
        {
            [_lock lock];
            if (value > 0)
            {
                [NSThread sleepForTimeInterval:1];
                NSLog(@"执行了一次哦");
                TestMethod(value--);
            }
            NSLog(@"是否执行到这里");
            [_lock unlock];
        };
        
        TestMethod(5);
    });
    

 执行结果:

2017-12-12 11:49:43.378299+0800 NSLock[1419:176157] 执行了一次哦
2017-12-12 11:49:44.380543+0800 NSLock[1419:176157] 执行了一次哦
2017-12-12 11:49:45.382145+0800 NSLock[1419:176157] 执行了一次哦
2017-12-12 11:49:46.387148+0800 NSLock[1419:176157] 执行了一次哦
2017-12-12 11:49:47.388813+0800 NSLock[1419:176157] 执行了一次哦
2017-12-12 11:49:48.389408+0800 NSLock[1419:176157] 执行了一次哦
2017-12-12 11:49:49.392983+0800 NSLock[1419:176157] 执行了一次哦
2017-12-12 11:49:50.396521+0800 NSLock[1419:176157] 执行了一次哦
2017-12-12 11:49:51.399108+0800 NSLock[1419:176157] 执行了一次哦
2017-12-12 11:49:52.399976+0800 NSLock[1419:176157] 执行了一次哦
2017-12-12 11:49:53.404280+0800 NSLock[1419:176157] 执行了一次哦
2017-12-12 11:49:54.409044+0800 NSLock[1419:176157] 执行了一次哦
2017-12-12 11:49:55.412670+0800 NSLock[1419:176157] 执行了一次哦
2017-12-12 11:49:56.413754+0800 NSLock[1419:176157] 执行了一次哦
2017-12-12 11:49:57.414257+0800 NSLock[1419:176157] 执行了一次哦

4. NSConditionLock 条件锁

 NSConditionLock相比NSLock多了一个condition参数,我们可以理解为一个条件标示。

@property (readonly)NSInteger condition; //这属性非常重要,外部传入的condition与之相同才会获取到lock对象,反之阻塞当前线程,直到condition相同
- (void)lockWhenCondition:(NSInteger)condition; //condition与内部相同才会获取锁对象并立即返回,否则阻塞线程直到condition相同
- (BOOL)tryLock;//尝试获取锁对象,获取成功需要配对unlock
- (BOOL)tryLockWhenCondition:(NSInteger)condition; //同上
- (void)unlockWithCondition:(NSInteger)condition; //解锁,并且设置lock.condition = condition

NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0];
    
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        if([cLock tryLockWhenCondition:0]){
            NSLog(@"线程1");
            [cLock unlockWithCondition:1];
        }else{
            NSLog(@"失败");
        }
    });
    
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [cLock lockWhenCondition:3];
        NSLog(@"线程2");
        [cLock unlockWithCondition:2];
    });
    
    //线程3
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [cLock lockWhenCondition:1];
        NSLog(@"线程3");
        [cLock unlockWithCondition:3];
    });

 输出结果:

2017-12-12 13:32:24.060013+0800 条件锁 - NSConditionLock[1783:250080] 线程1
2017-12-12 13:32:24.060461+0800 条件锁 - NSConditionLock[1783:250078] 线程3
2017-12-12 13:32:24.060626+0800 条件锁 - NSConditionLock[1783:250079] 线程2

  - 我们在初始化这个lock的时候,给定了它的初始条件为0;

  - 执行tryLockWhenCondition:时,我们传入的条件标示也是0,所以线程1加锁成功。

  - 执行unLockWithCondition:时,这时候会把condition将0改为1

  - 因为condition为1,所以先走线程3,然后线程3将condition修改为3,

  - 最后走了线程2

可以看出,NSConditionLock还可以实现任务之间的依赖。

5. NSCondition 

NSCondition实际上作为一个锁和一个线程检查器。锁主要是为了检测条件时保护数据源,执行条件引发的任务;线程检查器主要是根据条件决定是否继续进行下去,即线程是否被阻塞。

NSCondition实现了NSLocking协议,当多个线程访问同一段代码,会以wait为分水岭,一个线程等待另一个线程unlock后,才会走wait之后的代码。

[condition lock];//一般用于多线程同时访问、修改同一个数据源,保证在同一时间内数据源只被访问、修改一次,其他线程的命令需要在lock 外等待,只到unlock ,才可访问

[condition unlock];//与lock 同时使用

[condition wait];//让当前线程处于等待状态

[condition signal];//CPU发信号告诉线程不用在等待,可以继续执行

 我们可以来看一个生产者消费者的问题,

消费者取得锁,取产品,如果没有产品,则wait等待,这时候会释放锁,知道有线程去唤醒它去消费产品。

生产者制造产品,首先也要取得锁,然后生产,再发signal,这样可以唤醒wait的消费者

#import "ViewController.h"

@interface ViewController ()
@property (strong, nonatomic) NSCondition *condition;
@property (assign ,nonatomic) NSInteger goodNum;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    _goodNum = 0;
    
    _condition = [[NSCondition alloc] init];
    
    [NSThread detachNewThreadSelector:@selector(buyGoods) toTarget:self withObject:nil];
    
    [NSThread detachNewThreadSelector:@selector(shopGoods) toTarget:self withObject:nil];
    
}

- (void)buyGoods{
    [_condition lock];
    while (_goodNum == 0){
        NSLog(@"当前售卖个数为0,不能卖,等待");
        [_condition wait];
    }
    _goodNum--;
    NSLog(@"买了一个 - %ld",_goodNum);
    [_condition unlock];
}

- (void)shopGoods{
    [_condition lock];
    _goodNum++;
    NSLog(@"准备卖一个 - %ld",_goodNum);
    [_condition signal];
    NSLog(@"告诉买家可以买了");
    [_condition unlock];
}

 输出结果:

2017-12-12 14:43:48.995787+0800 NSCondition[2410:357340] 当前售卖个数为0,不能卖,等待
2017-12-12 14:43:48.996067+0800 NSCondition[2410:357341] 准备卖一个 - 1
2017-12-12 14:43:48.996578+0800 NSCondition[2410:357341] 告诉买家可以买了
2017-12-12 14:43:48.997806+0800 NSCondition[2410:357340] 买了一个 - 0

 我们可以看出,当消费者想要买东西的时候,因为商品初始数量为0,所以只能等待生产者去生产商品,准备售卖,有商品之后,会signal,告诉消费者可以买了。

6. 使用C语言的pthread_mutex_t实现的锁

#import "ViewController.h"
#import <pthread.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    __block pthread_mutex_t mutex;
    
    pthread_mutex_init(&mutex, NULL);
    
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        pthread_mutex_lock(&mutex);
        NSLog(@"线程1开始执行");
        for (NSInteger i = 1; i <= 10; i++) {
            sleep(1);
            NSLog(@"线程1 在执行 ---- %ld",i);
        }
        pthread_mutex_unlock(&mutex);
    });
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        pthread_mutex_lock(&mutex);
        NSLog(@"线程1执行完了,线程2开始执行");
        pthread_mutex_unlock(&mutex);
    });
    
}

 输出结果:

2017-12-12 14:54:16.343540+0800 pthread_mutex - 互斥锁[2518:371518] 线程1开始执行
2017-12-12 14:54:17.348019+0800 pthread_mutex - 互斥锁[2518:371518] 线程1 在执行 ---- 1
2017-12-12 14:54:18.348439+0800 pthread_mutex - 互斥锁[2518:371518] 线程1 在执行 ---- 2
2017-12-12 14:54:19.351159+0800 pthread_mutex - 互斥锁[2518:371518] 线程1 在执行 ---- 3
2017-12-12 14:54:20.355283+0800 pthread_mutex - 互斥锁[2518:371518] 线程1 在执行 ---- 4
2017-12-12 14:54:21.358290+0800 pthread_mutex - 互斥锁[2518:371518] 线程1 在执行 ---- 5
2017-12-12 14:54:22.361572+0800 pthread_mutex - 互斥锁[2518:371518] 线程1 在执行 ---- 6
2017-12-12 14:54:23.362013+0800 pthread_mutex - 互斥锁[2518:371518] 线程1 在执行 ---- 7
2017-12-12 14:54:24.363130+0800 pthread_mutex - 互斥锁[2518:371518] 线程1 在执行 ---- 8
2017-12-12 14:54:25.366042+0800 pthread_mutex - 互斥锁[2518:371518] 线程1 在执行 ---- 9
2017-12-12 14:54:26.370250+0800 pthread_mutex - 互斥锁[2518:371518] 线程1 在执行 ---- 10
2017-12-12 14:54:26.370496+0800 pthread_mutex - 互斥锁[2518:371519] 线程1执行完了,线程2开始执行

 

iOS中常见的锁

标签:blog   .com   images   bsp   异常处理   解决问题   test   property   max   

原文地址:http://www.cnblogs.com/chenjiangxiaoyu/p/8027046.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!