iOS 中的NSLock、 NSRecursiveLock、 NSCondition

  • 摘要:多线程编程中经常会碰到多个线程访问一个变量的问题,那么我们先来熟悉下我们跟线程相关的修饰符nonatomic和atomic一搜索,会有很多文章;但是这些文章有一个共同的特点那就是nonatomic多线程不安全和atomic多线程安全如何来判断线程安全或不安全?对于小公司在大多数项目说的简单点安全就是不报错,不安全就是报错我写了个demo验证了下@property(strong,nonatomic)NSMutableArray*arrList1;@property(strong
  • 多线程编程中经常会碰到多个线程访问一个变量的问题,那么我们先来熟悉下我们跟线程相关的修饰符nonatomicatomic一搜索,会有很多文章;但是这些文章有一个共同的特点那就是nonatomic多线程不安全atomic多线程安全如何来判断线程安全或不安全?对于小公司在大多数项目说的简单点安全就是不报错,不安全就是报错我写了个demo验证了下

    @property (strong, nonatomic) NSMutableArray *arrList1;@property (strong, atomic) NSMutableArray *arrList2;

    定义了两个变量最大的不同是一个用nonatomic,一个使用atomic然后,我开启了多个线程

    - (void)viewDidLoad { [super viewDidLoad]; 
    self.arrList1 = [[NSMutableArray alloc] init]; 
    self.arrList2 = [[NSMutableArray alloc] init]; 
    thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject1) object:NULL]; 
    thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject2) object:NULL]; 
    thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject3) object:NULL];
     thread4 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject3) object:NULL];
    }
    -(void)addObject1{ 
    for (int i = 0; i < 50; ++i) { 
    [self.arrList1 addObject:@"1111"];
     NSLog(@"%@",self.arrList1); 
    }
    }
    -(void)addObject2{
     for (int i = 0; i < 50; ++i) {
     [self.arrList1 addObject:@"aaaa"]; 
    NSLog(@"%@",self.arrList1); 
    }}
    -(void)addObject3{
     for (int i = 0; i < 50; ++i) {
     [self.arrList1 addObject:@"AAAA"]; 
    NSLog(@"%@",self.arrList1);
     }
    }
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event { [thread1 start]; [thread2 start]; [thread3 start];
    }

    三个线程都很给面子的访问两个不同的NSMutableArray确没有报错说好了线程不安全为啥支持多线程访问了?不知道是OC做了修改还是其他原因,怎么跟Java中StringBuffer和StringBuilder一样???说好了不安全,怎么可以多线程访问 ???不解


    上面扯到的东西貌似跟今天我们讲到的锁没什么联系,但是如果你继续搜索下去的话不少人介绍说跟锁有关系,但是我看了很多篇日志或文章没找到一条论证的代码,全都是文字介绍—这就扯犊子了,没有代码说明所有的结论都是废的


    :我们可以理解为 变量在某一段时间只能被一个线程访问 貌似这个概念抽象的一塌糊涂;来段代码加日志截图,说说情况和原因 
    在上面的基础上,方便看到日志;我们把代码改为循环10次

    -(void)addObject1{
     for (int i = 0; i < 10; ++i) {
     [arrList addObject:@"1111"]; 
    NSLog(@"%@" ,arrList);
     }
    }
    -(void)addObject2{
     for (int i = 0; i < 10; ++i) { 
    [arrList addObject:@"AAAA"];
     NSLog(@"%@" ,arrList);
     }
    }
    -(void)addObject3{ 
    for (int i = 0; i < 10; ++i) {
     [arrList addObject:@"aaaa"];
     NSLog(@"%@" ,arrList); 
    }}

    注意这里是把循环50次变为循环10次,为了看到日志;三个方法分别对应三个线程;我们的理解是线程1,2,3谁先执行是不确定的但是按照我们的感觉是输出的结果: 
    1111111111AAAAAAAAAAaaaaaaaaaa 
    或者 
    AAAAAAAAAA1111111111aaaaaaaaaa 
    或者类似这种排列,也就是输出连贯的,再执行下一个连贯的循环输出 ,但是我们看下我们的日志 
    ios中的锁(Lock) NSLock,NSConditionLock,NSRecursiveLock,NSCondition_IOS

    看到这样的日志输出,我们仿佛对多线程有了那么一点点的了解,for循环体还没有结束而循环体里面的可变数组却被两个的线程操作,这个就尴尬了那么如何得到上面顺序排列的值了,比如:AAAAAAAAAA1111111111aaaaaaaaaa;那么我们需要做的就是加锁防止循环没有结束而被另外的线程访问,我们来介绍下ios中的所谓的锁


    1. 关键字synchronized

    如果从Java转过来,那么很容易理解了 ,Java中这个关键字使用的比较频繁;这个关键字是修饰一个对象的,这个很关键!!!

    -(void)addObject1{ 
    @synchronized (arrList) { 
    for (int i = 0; i < 10; ++i) { 
    [arrList addObject:@"1111"];
     NSLog(@"%@" ,arrList); } 
    }}

    同样在addObject2和addObject3方法中也要这样写,这样写神马意思了 ? synchronized 是同步的意思,或者这样理解通过synchronized 把 arrList关起来等这段代码(被synchronized大括号括起来的代码 )执行完之后再放出来,很显然关起来的时候只允许一个线程对它操作,来张截图 
    ios中的锁(Lock) NSLock,NSConditionLock,NSRecursiveLock,NSCondition_IOS
    这结果简直就是强迫症的福音

    2. NSLock 锁

    这是OC中的一把锁,那么跟刚刚提到的synchronized有什么区别了 ?刚刚上文提到synchronized只能给某一个对象加锁,而NSLock可以个一段代码加锁;首先简单的看下OC中有几把锁 
    ios中的锁(Lock) NSLock,NSConditionLock,NSRecursiveLock,NSCondition_IOS

    一共四把锁:NSLock、 NSConditionLock、 NSRecursiveLock 、NSCondition;但是他们不是继承某一个类,而是都实现了NSLocking这个代理,那么必须看看这个代码有神马东西

    扫描二维码关注公众号,回复: 5760030 查看本文章
    @protocol NSLocking- (void)lock;- (void)unlock;@end

    这个代理还是比较老实的,就提供两个方法:加锁(lock)和解锁(unlock)

    2.1 NSLock普通锁

    刚刚上面说过NSLock是锁一段代码的,那么这个就比较简单了,首先在ViewDidLoad中初始化NSLock ;然后使用这个锁,上代码

    - (void)viewDidLoad {
     [super viewDidLoad]; 
    /*其他的代码省略三千个字...*/ 
    nslock = [[NSLock alloc] init];
    }
    -(void)addObject1{
     [nslock lock]; 
    for (int i = 0; i < 10; ++i) { 
    [arrList addObject:@"1111"];
     NSLog(@"%@", arrList); 
    }
     [nslock unlock];
    }
    -(void)addObject2{ 
    [nslock lock];
     for (int i = 0; i < 10; ++i) {
     [arrList addObject:@"AAAA"]; 
    NSLog(@"%@", arrList);
     } 
    [nslock unlock];
    }
    -(void)addObject3{ 
    [nslock lock]; 
    for (int i = 0; i < 10; ++i) {
     [arrList addObject:@"aaaa"];
    NSLog(@"%@", arrList); 
    } 
    [nslock unlock];
    }
    
    也就是 从 [nslock lock] —> [nslock unlock] 这中间的代码块被锁定只允许一个线程操作,等到循环完成之后自动执行解锁操作;这里我们可以看出synchronized 和 NSLock的区别
    名称 特点
    synchronized 对一个对象加锁
    NSLock 对一段代码加锁

    2.2 NSConditionLock 条件锁

    条件锁看到了源码,我们可以这么理解:在指定条件下加锁(lock)和解锁(unlock)

    @interface NSConditionLock : NSObject <NSLocking> {
    @private void *_priv;
    }
    - (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
    @property (readonly) NSInteger condition;
    - (void)lockWhenCondition:(NSInteger)condition;
    - (BOOL)tryLock;- (BOOL)tryLockWhenCondition:(NSInteger)condition;
    - (void)unlockWithCondition:(NSInteger)condition;
    - (BOOL)lockBeforeDate:(NSDate *)limit;
    - (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
    @property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);@end

    不难看出它的初始化方法包含有一个条件,这个条件是个自定义的值;到XXX时候加锁(lock)或解锁(unlock)这个锁到底怎么用了 ?我发现网上都他妈没有讲清楚,而且是纯扯犊子 。。。 这个锁比较难理解,可以说是最难理解的一个锁了,先上代码和日志

    - (void)viewDidLoad {
     [super viewDidLoad]; 
    thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject1) object:NULL]; 
    thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject2) object:NULL]; 
    nslock = [[NSConditionLock alloc] init];
    }
    -(void)addObject1{ 
    NSLog(@"1111111111111111");
     for (int i = 0; i < 10; ++i) {
     [nslock lock]; 
    [nslock unlockWithCondition:i];
     NSLog(@"?????????");
     [NSThread sleepForTimeInterval:1];
     }}
    -(void)addObject2{ 
    NSLog(@"22222222222222222");
     [nslock lockWhenCondition:2]; 
    NSLog(@"##############");
     [nslock unlock];
    }
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event { [thread1 start]; [thread2 start];
    }

    我们看下日志输出神马东西。。。 
    ios中的锁(Lock) NSLock,NSConditionLock,NSRecursiveLock,NSCondition_IOS

    我们分析下代码日志输出和代码: 
    当两个线程启动的时候显然两个方法addObject1addObject2都执行了 ,因为我们可以看到1111111111111111 和 22222222222222222但是,并没有执行addObject2下面的 NSLog(@”##############”);这个方法,而是直接执行addObject1的方法,说明: 
    lockWhenCondition没有向下执行而是也就是上锁失败,当条件变成2的时候上锁成功 
    NSConditionLock(条件锁)总结

    • NSConditionLock 是锁,一旦一个线程获得锁,其他线程一定等待
    • [xxxx lock]; 表示 xxx 期待获得锁,如果没有其他线程获得锁(不需要判断内部的condition)那它能执行此行以下代码,如果已经有其他线程获得锁(可能是条件锁,或者无条件锁), 则等待,直至其他线程解锁
    • [xxx lockWhenCondition:A条件]; 表示如果没有其他线程获得该锁,但是该锁内部的condition不等于A条件,它依然不能获得锁,仍然等待。如果内部的condition等于A条件,并且没有其他线程获得该锁,则进入代码区,同时设置它获得该锁,其他任何线程都将等待它代码的完成,直至它解锁。
    • [xxx unlockWithCondition:A条件]; 表示释放锁,同时把内部的condition设置为A条件
    • return = [xxx lockWhenCondition:A条件 beforeDate:A时间]; 表示如果被锁定(没获得锁),并超过该时间则不再阻塞线程。但是注意:返回的值是NO,它没有改变锁的状态, 这个函数的目的在于可以实现两种状态下的处理
    • 所谓的condition就是整数,内部通过整数比较条件

    2.3 NSRecursiveLock 递归锁

    递归锁是一种特殊的NSLock主要用在递归里面,上代码

    - (void)viewDidLoad { 
    [super viewDidLoad];
     thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject1) object:NULL];
     nslock = [[NSRecursiveLock alloc] init];
    }
    -(void)addObject1{ 
    [self sumTotal:20];、
    }
    -(void)sumTotal:(long)value{
     [nslock lock]; 
    if(value >0 ){
     value -- ;
     NSLog(@"%d",value);
     [self sumTotal:value]; 
    } 
    [nslock unlock];
    }
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event { [thread1 start];
    }

    如果不用NSRecursiveLock会怎么样了 ?也就是把上面的NSRecursiveLock换成NSLock,来张日志截图吧。。。 
    ios中的锁(Lock) NSLock,NSConditionLock,NSRecursiveLock,NSCondition_IOS

    2.4 NSCondition 断言

    这应该是跟条件锁一样很操蛋的锁,因为这个锁比较特殊,看看它的方法就觉得它奇葩了

    @interface NSCondition : NSObject <NSLocking> {
    @private void *_priv;
    }
    - (void)wait;
    - (BOOL)waitUntilDate:(NSDate *)limit;- (void)signal;
    - (void)broadcast;@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);
    @end

    wait:等待线程 
    signal:唤醒一个指定线程 
    broadcast:唤醒所有线程
     
    我们可以通过这种比较简单的描述来理解下 ,老套路,上代码;比较老掉牙的消费者和生产者

    - (void)viewDidLoad {
     [super viewDidLoad];
     arrList = [[NSMutableArray alloc] init]; 
    thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject1) object:NULL]; 
    thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject2) object:NULL];
     nslock = [[NSCondition alloc] init];
    UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(10, 10, 100, 100)]; button.backgroundColor = [UIColor redColor]; 
    [button addTarget:self action:@selector(button1) forControlEvents:UIControlEventTouchUpInside];
     [self.view addSubview:button]; 
    button = [[UIButton alloc] initWithFrame:CGRectMake(10, 210, 100, 100)]; button.backgroundColor = [UIColor greenColor]; 
    [button addTarget:self action:@selector(button2) forControlEvents:UIControlEventTouchUpInside];
     [self.view addSubview:button];
    }
    -(void)button1{ 
    [thread1 start];
    }
    -(void)button2{
    [thread2 start];
    }
    -(void)addObject1{ 
    [nslock lock]; 
    while ([arrList count] == 0) {
     NSLog(@"等待生产者 生产 !!!!"); 
    [nslock wait];
     }
     [arrList removeObjectAtIndex:0];
     NSLog(@"消费者消费了一个 产品");
     [nslock unlock];
    }
    -(void)addObject2{ 
    [nslock lock];
     [arrList addObject:@"我是一个产品"]; NSLog(@"产生了一个产品"); [nslock signal]; [nslock unlock];
    }

    先启动消费者,消费者会等待生产者生产,来个日志 
    ios中的锁(Lock) NSLock,NSConditionLock,NSRecursiveLock,NSCondition_IOS

    OC中的锁就介绍到这里,当然还去其他的C写的锁;这里就不讨论了 。。。

  • 以上是ios中的锁(Lock) NSLock,NSConditionLock,NSRecursiveLock,NSCondition的内容,更多 NSConditionLockNSRecursiveLock NSCondition NSLock lock iOS 

猜你喜欢

转载自blog.csdn.net/Lea__DongYang/article/details/84401819