iOS中常见的锁

多线程的安全隐患

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

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

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

比如一个买票问题:

#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开始执行

 

时间: 2024-10-13 07:24:03

iOS中常见的锁的相关文章

iOS开发——高级篇——iOS中常见的设计模式(MVC/单例/委托/观察者)

关于设计模式这个问题,在网上也找过一些资料,下面是我自己总结的,分享给大家 如果你刚接触设计模式,我们有好消息告诉你!首先,多亏了Cocoa的构建方式,你已经使用了许多的设计模式以及被鼓励的最佳实践. 首先得搞清楚设计模式是个什么鬼,在软件设计领域,设计模式是对通用问题的可复用的解决方案.设计模式是一系列帮你写出更可理解和复用代码的模板,设计模式帮你创建松耦合的代码以便你不需要费多大力就可以改变或者替换代码中的组件 其实iOS中的设计模式有非常多,常用的就下面这四种 一.MVC设计模式(设计模式

Java线程并发中常见的锁--自旋锁 偏向锁

随着互联网的蓬勃发展,越来越多的互联网企业面临着用户量膨胀而带来的并发安全问题.本文着重介绍了在java并发中常见的几种锁机制. 1.偏向锁 偏向锁是JDK1.6提出来的一种锁优化的机制.其核心的思想是,如果程序没有竞争,则取消之前已经取得锁的线程同步操作.也就是说,若某一锁被线程获取后,便进入偏向模式,当线程再次请求这个锁时,就无需再进行相关的同步操作了,从而节约了操作时间,如果在此之间有其他的线程进行了锁请求,则锁退出偏向模式.在JVM中使用-XX:+UseBiasedLocking pac

Java线程并发中常见的锁

随着互联网的蓬勃发展,越来越多的互联网企业面临着用户量膨胀而带来的并发安全问题.本文着重介绍了在java并发中常见的几种锁机制. 1.偏向锁 偏向锁是JDK1.6提出来的一种锁优化的机制.其核心的思想是,如果程序没有竞争,则取消之前已经取得锁的线程同步操作.也就是说,若某一锁被线程获取后,便进入偏向模式,当线程再次请求这个锁时,就无需再进行相关的同步操作了,从而节约了操作时间,如果在此之间有其他的线程进行了锁请求,则锁退出偏向模式.在JVM中使用-XX:+UseBiasedLocking pac

iOS中常见 Crash 及解决方案

一.访问了一个已经被释放的对象 在不使用 ARC 的时候,内存要自己管理,这时重复或过早释放都有可能导致 Crash. 例子 NSObject * aObj = [[NSObject alloc] init]; [aObj release]; NSLog(@"%@", aObj); 原因 aObj 这个对象已经被释放,但是指针没有置空,这时访问这个指针指向的内存就会 Crash. 解决办法 使用前要判断非空,释放后要置空.正确的释放应该是: [aObj release]; aObj =

Ios中常见的问题

__block和__weak修饰符的区别其实是挺明显的: 1.__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型. 2.__weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int). 3.__block对象可以在block中被重新赋值,__weak不可以. tableView 滑动卡的问题主要是因为:从缓存中或者是从本地读取图片给UIImage的时候耗费的时间.需要把下面的两句话放到子线程里面: NSData *i

iOS中常见的设计模式

一.单例模式 1. 什么是单例模式? 在iOS应用的生命周期中,某个类只有一个实例. 2. 单例模式解决了什么问题? 想象一下,如果我们要读取文件配置信息,那么每次要读取,我们就要创建一个文件实例,然后才能获取到里面的相关配置信息,这样如果,我们如果要多次读取这个文件的配置信息,那就要创建多个实例,这样严重浪费了内存资源.而实际应用中,当我们要用到的类可能是要反复用到的,一般可以考虑使用单例模式.这样可以大大降低创建新实例带来的内存浪费. 3. 单例模式的实现原理 一般会封装一个静态属性,并提供

iOS中常见的 Crash 场景以及解决方法

1. 常见的 Crash 场景 访问了僵尸对象 访问了不存在的方法 数组越界 在定时器下一次回调前将定时器释放,会Crash 2.  关于BAD_ACCESS  出现的原因: 访问了野指针, 比如访问已经释放对象的成员变量或者发消息, 死循环等; 解决方法: 1.  重写对象的respondsToSelector 方法, 先找到出现 EXECBADACCESS 前访问的最后一个 object; 2. 设置Enable Zombie Objects; 3. 设置全局断点快速定位问题代码所在行,接收

ios中常见的几种控件.(UISlider,UISwitch,UIStepper,UISegmentedControl)

一.滑块控件(UISlider)  效果如下 注:系统原来效果如下 具体功能运用: ①创建滑块控件(UISlider),设置控件大小,并添加到window视图上,后释放 UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(20, 30, 280, 100)]; [self.window addSubview:slider];  [slider release]; ②设置最大值,最小值,以及开始时滑动条所在的位置 //设置最大

iOS中常见的内存问题

iOS中用retain或者copy修饰的属性就要重写dealloc方法, 在dealloc中把属性release. 这是因为当我们用self.取属性的时候会让属性的引用计数加1. 如果不用self.而是用"_"取属性那就不用重写dealloc方法, 因为"_"并没有让属性的引用计数加1.  总而言之, 在iOS开发中只要用self.访问属性就一定要重写dealloc方法, 如果用"_"就不同重写dealloc, 否则会有过度释放从而引发程序cra