iOS-线程锁

作者: Harry__Li | 来源:发表于2020-12-25 16:37 被阅读0次

总有人会问线程安全问题,自己平常在项目中也没怎么遇到,没办法了,自能自己上网查找,最后总结了。

线程锁用来干什么的

我们要知道为什么要加线程锁?线程锁有什么作用?

-(void)testthread{
    for (int i=0; i<20; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
           
            self.num++;
            NSLog(@"第%d个循环 值是:%ld",i,self.num);
        });
    }      
}

输出的结果是

2020-12-23 16:55:33.875182+0800 LBDaySurgery[17491:7200021] 第0个循环 值是:1
2020-12-23 16:55:33.875227+0800 LBDaySurgery[17491:7200021] 第2个循环 值是:3
2020-12-23 16:55:33.875224+0800 LBDaySurgery[17491:7200019] 第1个循环 值是:2
2020-12-23 16:55:33.875241+0800 LBDaySurgery[17491:7200021] 第3个循环 值是:4
2020-12-23 16:55:33.875255+0800 LBDaySurgery[17491:7200021] 第4个循环 值是:5
2020-12-23 16:55:33.875266+0800 LBDaySurgery[17491:7200021] 第5个循环 值是:6
2020-12-23 16:55:33.875281+0800 LBDaySurgery[17491:7200021] 第6个循环 值是:7
2020-12-23 16:55:33.875303+0800 LBDaySurgery[17491:7200018] 第8个循环 值是:9
2020-12-23 16:55:33.875308+0800 LBDaySurgery[17491:7200019] 第9个循环 值是:9
2020-12-23 16:55:33.875320+0800 LBDaySurgery[17491:7200021] 第10个循环 值是:10
2020-12-23 16:55:33.875319+0800 LBDaySurgery[17491:7200022] 第7个循环 值是:8

每次输出的结果都可以是不一样的,而且它也是不是按顺序出来的。这个就叫线程不安全。
就是在共享数据 num的时候,我们没办法控制它,不知道它会出现什么结果,那肯定就是不安全的。它都脱离控制了,指不定就出现什么bug了。
这时候就需要锁了。
锁的机制来保证线程安全,即确保同一时刻只有同一个线程来对同一个数据源进行访问。当然单线程的不会用到锁,因为它不会造成争抢数据资源的情况。

锁的分类

锁大方向是可以分为互斥锁和自旋锁的。
相同点:都可以避免线程访问同一个数据的时候造成混乱。
不同点:

1.互斥锁:如果共享数据已经有其他线程加锁了,线程会进入休眠状态等待锁。一旦被访问的资源被解锁, 则等待资源的线程会被唤醒。
2.自旋锁:如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问的资源被解锁, 则等待资源的线程会立即执行。

特点

1.自旋锁的性能高于互斥锁,因为响应速度快
2.自旋锁虽然会一直自旋等待获取锁,但不会一直占用CPU,超过了操作系统分配的时间片会被强制挂起
3.自旋锁如果不能保证所有线程都是同一优先级,则可能造成死锁。

1.@synchronized

我们先上一段代码在慢慢的来分析

/// Synchronized 锁
-(void)testSynchronized{

    NSObject *object=[NSObject new];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        @synchronized (object) {
            NSLog(@"线程1开始");
            sleep(2);
            NSLog(@"线程1结束");
        }
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        @synchronized (object) {
            NSLog(@"线程2结束");
        }
    });
    
}

@synchronized(cjobj) 指令使用的 object 为该锁的唯一标识,只有当标识相同时,才为满足互斥锁。最后的输出结果为

2020-12-23 17:32:50.826307+0800 LBDaySurgery[17534:7215824] 线程1开始
2020-12-23 17:32:52.831491+0800 LBDaySurgery[17534:7215824] 线程1结束
2020-12-23 17:32:52.831928+0800 LBDaySurgery[17534:7215828] 线程2结束

在第一个线程结束以后,第二个线程才开始。
加入我们把@synchronized(cjobj) 标识换一下又会输出什么呢?

2020-12-23 17:51:15.594909+0800 LBDaySurgery[17549:7221916] 线程1开始
2020-12-23 17:51:16.600554+0800 LBDaySurgery[17549:7221921] 线程2结束
2020-12-23 17:51:17.600929+0800 LBDaySurgery[17549:7221916] 线程1结束

在改变标识之后,不再为互斥锁。
@synchronized 指令实现锁的优点就是我们不需要在代码中显式的创建锁对象,不需要自己去加锁、解锁。便可以实现锁的机制,但作为一种预防措施,@synchronized 块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。所以如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。

@sychronized(cjobj){} 内部 cjobj 被释放或被设为 nil 不会影响锁的功能,但如果 cjobj 一开始就是 nil,那就会丢失了锁的功能了。

NSLock系列
1.NSLock

NSLock是最基本的互斥锁。我们点进NSLock可以发现有一下的方法

@protocol NSLocking
- (void)lock;
- (void)unlock;
@end

@interface NSLock : NSObject <NSLocking> {
@private
    void *_priv;
}

- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

我们来一个一个的使用一下。
通过 lock 和 unlock 来进行锁定和解锁。

 NSLock *objclock=[NSLock new];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       
        [objclock lock];
        NSLog(@"线程1加锁成功");
        sleep(2);
        [objclock unlock];
        NSLog(@"线程1解锁成功");
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        [objclock lock];
        NSLog(@"线程2加锁成功");
        [objclock unlock];
        NSLog(@"线程2解锁成功");
    });

创建NSLock对象,在两个线程中都加入了锁,所以,按照从上而下的顺序执行方法。先执行线程1的方法。 在执行完线程1之后在执行线程2。而在线程2中,sleep(1)并不在锁里面,所以它还是按照我们异步并发执行的 不需要等待它执行完成,在执行后面的。自然的最后的输出结果也就是下面的这个了:

2020-12-24 10:29:02.005469+0800 LBDaySurgery[18086:7451453] 线程1加锁成功
2020-12-24 10:29:04.010665+0800 LBDaySurgery[18086:7451453] 线程1解锁成功
2020-12-24 10:29:04.010733+0800 LBDaySurgery[18086:7451454] 线程2加锁成功
2020-12-24 10:29:04.010902+0800 LBDaySurgery[18086:7451454] 线程2解锁成功

tryLock能加锁返回 YES 并执行加锁操作,相当于 lock,反之返回 NO。

 NSLock *objclock=[NSLock new];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       
        [objclock lock];
        NSLog(@"线程1加锁成功");
        sleep(2);
        [objclock unlock];
        NSLog(@"线程1解锁成功");
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //能加锁返回 YES 并执行加锁操作,相当于 lock,反之返回 NO
        if ([objclock tryLock]) {
            NSLog(@"线程2加锁成功");
            [objclock unlock];
        }else{
        NSLog(@"线程2加锁失败");
        }
    });
2020-12-24 11:03:50.786699+0800 LBDaySurgery[18175:7467087] 线程1加锁成功
2020-12-24 11:03:50.786780+0800 LBDaySurgery[18175:7467083] 线程2加锁失败
2020-12-24 11:03:52.791891+0800 LBDaySurgery[18175:7467087] 线程1解锁成功

在线程1执行的时候,线程2也执行,因为线程1加锁了,在用trylock判断的时候不能加锁,因为互斥锁不能同时加载多个锁。返回的是NO,然后执行else中的语句。
lockBeforeDate: 方法会在所指定 Date 之前尝试加锁,会阻塞线程,如果在指定时间之前都不能加锁,则返回 NO,指定时间之前能加锁,则返回 YES。

NSLock *objclock=[NSLock new];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       
        [objclock lock];
        NSLog(@"线程1加锁成功");
        sleep(2);
        [objclock unlock];
        NSLog(@"线程1解锁成功");
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        if ([objclock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]) {
            NSLog(@"线程3加锁成功");
            [objclock unlock];
            NSLog(@"线程3解锁");
        }else{
        NSLog(@"线程3加锁失败");
        }
    });
2020-12-24 11:15:40.930892+0800 LBDaySurgery[18186:7471400] 线程1加锁成功
2020-12-24 11:15:42.937279+0800 LBDaySurgery[18186:7471400] 线程1解锁成功
2020-12-24 11:15:42.937349+0800 LBDaySurgery[18186:7471404] 线程3加锁成功
2020-12-24 11:15:42.937692+0800 LBDaySurgery[18186:7471404] 线程3解锁

在指定的10秒内加锁,返回了YES。最后的输出结果也就是加锁成功。如果我们吧10改成1,那自然就加锁失败了。
上面的这些例子最后也说明,互斥锁,当一个线程进行访问的时候,该线程获得锁,其他线程进行访问的时候,将被操作系统挂起,直到该线程释放锁,其他线程才能对其进行访问,从而却确保了线程安全。但是如果连续锁定两次,则会造成死锁问题。

2.NSConditionLock

NSConditionLock 对象所定义的互斥锁可以在使得在某个条件下进行锁定和解锁,它和 NSLock 类似,都遵循 NSLocking 协议,方法都类似,只是多了一个 condition 属性,以及每个操作都多了一个关于 condition 属性的方法,例如 tryLock、tryLockWhenCondition:,所以 NSConditionLock 可以称为条件锁。
用法如下:

 //初始化
    NSConditionLock *objclock=[[NSConditionLock alloc]initWithCondition:0];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       
        [objclock lock];
        NSLog(@"线程1加锁成功");
        sleep(2);
        [objclock unlock];
        NSLog(@"线程1解锁成功");
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       
        [objclock lockWhenCondition:0];
        sleep(1);
        NSLog(@"线程2加锁成功");
        [objclock unlock];
        NSLog(@"线程2解锁成功");
        
    });
2020-12-24 13:59:38.884752+0800 LBDaySurgery[18303:7532532] 线程1加锁成功
2020-12-24 13:59:40.889922+0800 LBDaySurgery[18303:7532532] 线程1解锁成功
2020-12-24 13:59:41.894050+0800 LBDaySurgery[18303:7532536] 线程2加锁成功
2020-12-24 13:59:41.894420+0800 LBDaySurgery[18303:7532536] 线程2解锁成功

--- 只有 condition 参数与初始化时候的 condition 相等,lock 才能正确进行加锁操作。
下面在看其他函数用法

/// 条件锁
-(void)testconditionlock{
    
    //初始化
    NSConditionLock *objclock=[[NSConditionLock alloc]initWithCondition:0];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       
        [objclock lock];
        NSLog(@"线程1加锁成功");
        sleep(2);
        [objclock unlock];
        NSLog(@"线程1解锁成功");
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       
        sleep(3);
        if ([objclock tryLockWhenCondition:0]) {
            NSLog(@"线程2加锁成功");
            sleep(2);
            [objclock unlockWithCondition:2];
            NSLog(@"线程2解锁成功");
        }else{
            NSLog(@"线程2加锁失败");
        }
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       
        sleep(1);
        [objclock lockWhenCondition:1];
        NSLog(@"线程3加锁成功");
        [objclock unlock];
        NSLog(@"线程3解锁成功");
    });
    
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       
        if ([objclock lockWhenCondition:2 beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]) {
            NSLog(@"线程4加锁成功");
            [objclock unlockWithCondition:1];
            NSLog(@"线程4解锁成功");
        }else{
            NSLog(@"线程4解锁失败");
        }
    });    
}

我们来分析一下:从上到下创建线程1,加锁解锁,然后睡眠3秒判断能否给条件为0的加锁成功。睡眠3秒,线程1已经执行完成了。所以线程2肯定能加锁成功,加入线程2中不是3秒而是1秒,那就不能加锁成功,因为线程1还没执行完。 我们注意到在线程2解锁的时候,没有用到unlock,而是用了unlockWithCondition,这个函数其实是做了两个事情,解锁、把条件改为2.所以线程3暂时不能加锁。对应的线程4的条件是2。在10秒的间隔内加锁成功。解锁并把条件改为1,线程3自然也就能加锁了。最后的输出结果为:

2020-12-24 14:44:02.223297+0800 LBDaySurgery[18356:7548358] 线程1加锁成功
2020-12-24 14:44:04.228443+0800 LBDaySurgery[18356:7548358] 线程1解锁成功
2020-12-24 14:44:05.228376+0800 LBDaySurgery[18356:7548356] 线程2加锁成功
2020-12-24 14:44:07.233620+0800 LBDaySurgery[18356:7548356] 线程2解锁成功
2020-12-24 14:44:07.233620+0800 LBDaySurgery[18356:7548355] 线程4加锁成功
2020-12-24 14:44:07.233762+0800 LBDaySurgery[18356:7548355] 线程4解锁成功
2020-12-24 14:44:07.233771+0800 LBDaySurgery[18356:7548353] 线程3加锁成功
2020-12-24 14:44:07.233833+0800 LBDaySurgery[18356:7548353] 线程3解锁成功
3.NSRecursiveLock

NSRecursiveLock 是递归锁,顾名思义,可以被一个线程多次获得,而不会引起死锁。它记录了成功获得锁的次数,每一次成功的获得锁,必须有一个配套的释放锁和其对应,这样才不会引起死锁。NSRecursiveLock 会记录上锁和解锁的次数,当二者平衡的时候,才会释放锁,其它线程才可以上锁成功。

//初始化
    NSRecursiveLock *objclock=[[NSRecursiveLock alloc]init];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void(^RecursiveBlock)(int);
        RecursiveBlock=^(int value){
            [objclock lock];
            NSLog(@"%d加锁成功",value);
            if (value>0) {
                NSLog(@"value:%d", value);
                RecursiveBlock(value - 1);
            }
            [objclock unlock];
            NSLog(@"%d解锁成功",value);
        };
        RecursiveBlock(4);
    });
2020-12-25 10:49:41.718732+0800 LBDaySurgery[19155:7923023] 4加锁成功
2020-12-25 10:49:41.718808+0800 LBDaySurgery[19155:7923023] value:4
2020-12-25 10:49:41.718840+0800 LBDaySurgery[19155:7923023] 3加锁成功
2020-12-25 10:49:41.718864+0800 LBDaySurgery[19155:7923023] value:3
2020-12-25 10:49:41.718888+0800 LBDaySurgery[19155:7923023] 2加锁成功
2020-12-25 10:49:41.718908+0800 LBDaySurgery[19155:7923023] value:2
2020-12-25 10:49:41.718931+0800 LBDaySurgery[19155:7923023] 1加锁成功
2020-12-25 10:49:41.718951+0800 LBDaySurgery[19155:7923023] value:1
2020-12-25 10:49:41.719020+0800 LBDaySurgery[19155:7923023] 0加锁成功
2020-12-25 10:49:41.719092+0800 LBDaySurgery[19155:7923023] 0解锁成功
2020-12-25 10:49:41.719182+0800 LBDaySurgery[19155:7923023] 1解锁成功
2020-12-25 10:49:41.719255+0800 LBDaySurgery[19155:7923023] 2解锁成功
2020-12-25 10:49:41.719324+0800 LBDaySurgery[19155:7923023] 3解锁成功
2020-12-25 10:49:41.719353+0800 LBDaySurgery[19155:7923023] 4解锁成功

如果我们使用NSLock来加锁就会造成 死锁。就像如下代码:

 //创建锁
    NSLock *lock = [[NSLock alloc] init];
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        static void(^TestMethod)(int);
        TestMethod = ^(int value)
        {
            NSLog(@"加锁%d",value);
            [lock lock];
            if (value > 0)
            {
                [NSThread sleepForTimeInterval:1];
                NSLog(@"休眠%d",value);
                value--;
                TestMethod(value);
            }
            NSLog(@"解锁%d",value);
            [lock unlock];
        };
        TestMethod(5);
        NSLog(@"结束");
    });

上面这段代码为什么造成死锁呢。加锁成功以后,value>0,执行TestMethod(4),这个时候需要加锁,但是在TestMethod(5)的时候没解锁,4中就没办法加锁,只能挂起等待。而在5中呢,是在等待4执行完,没办法解锁。所以造成了死锁。

4.NSCondition

NSCondition 是通过它可以实现不同线程的调度。一个线程被某一个条件所阻塞,直到另一个线程满足该条件从而发送信号给该线程使得该线程可以正确的执行。比如说,你可以开启一个线程下载图片,一个线程处理图片。这样的话,需要处理图片的线程由于没有图片会阻塞,当下载线程下载完成之后,则满足了需要处理图片的线程的需求,这样可以给定一个信号,让处理图片的线程恢复运行。

@interface NSCondition : NSObject <NSLocking> {
@private
    void *_priv;
}- (void)wait; //挂起线程
- (BOOL)waitUntilDate:(NSDate *)limit; //什么时候挂起线程
- (void)signal; // 唤醒一条挂起线程
- (void)broadcast; //唤醒所有挂起线程
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

下面来看看它的使用:

-(void)testcondition{
    
    //创建
    NSCondition *objeccondition=[[NSCondition alloc]init];
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [objeccondition lock];
        NSLog(@"任务一加锁成功");
        [objeccondition wait];
        [objeccondition unlock];
        NSLog(@"任务1解锁成功");
        
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [objeccondition lock];
        NSLog(@"任务2加锁成功");
        if ([objeccondition waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]]) {
            NSLog(@"任务2被唤醒了");
            [objeccondition unlock];
        }
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(3);
        //唤醒全部等待的任务
        [objeccondition broadcast];
    });   
}

2020-12-25 13:59:01.768743+0800 LBDaySurgery[19293:7988965] 任务一加锁成功
2020-12-25 13:59:01.768839+0800 LBDaySurgery[19293:7988963] 任务2加锁成功
2020-12-25 13:59:04.774042+0800 LBDaySurgery[19293:7988965] 任务1解锁成功
2020-12-25 13:59:04.774045+0800 LBDaySurgery[19293:7988963] 任务2被唤醒了
三.dispatch_semaphore

dispatch_semaphore 使用信号量机制实现锁,等待信号和发送信号。

1.dispatch_semaphore 是 GCD 用来同步的一种方式,与他相关的只有三个函数,一个是创建信号量,一个是等待信号,一个是发送信号。
2.dispatch_semaphore 的机制就是当有多个线程进行访问的时候,只要有一个获得了信号,其他线程的就必须等待该信号释放。

dispatch_semaphore_create(long value);
dispatch_semaphore_wait(dispatch_semaphore_t _Nonnull dsema, dispatch_time_t timeout);
dispatch_semaphore_signal(dispatch_semaphore_t _Nonnull dsema);

用法如下:

/// 信号量
-(void)testdispatch_semaphore{
       
    dispatch_semaphore_t objc=dispatch_semaphore_create(2);
    dispatch_time_t time=dispatch_time(DISPATCH_TIME_NOW, 6*NSEC_PER_SEC);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_wait(objc, time);
        NSLog(@"线程1开始");
        sleep(5);
        NSLog(@"线程1结束");
        dispatch_semaphore_signal(objc);
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{        
        sleep(1);
        dispatch_semaphore_wait(objc, time);
        NSLog(@"线程2开始");
        dispatch_semaphore_signal(objc);        
    });    
}
2020-12-25 14:28:04.459866+0800 LBDaySurgery[19315:7997946] 线程1开始
2020-12-25 14:28:05.465068+0800 LBDaySurgery[19315:7997948] 线程2开始
2020-12-25 14:28:09.465176+0800 LBDaySurgery[19315:7997946] 线程1结束

1.dispatch_semaphore_create (x)创建信号。参数x代表的是可以有x个线程可以同时访问。上面的代码中可以同时被2个线程访问。这也就解释了为什么这个参数一定要大于0.假如x为1,这儿输出的就又不一样了。他需要等线程1执行完,线程2在执行(先忽略限制时间time)

2020-12-25 14:35:25.112968+0800 LBDaySurgery[19320:8000170] 线程1开始
2020-12-25 14:35:30.118062+0800 LBDaySurgery[19320:8000170] 线程1结束
2020-12-25 14:35:30.118367+0800 LBDaySurgery[19320:8000167] 线程2开始
  1. dispatch_time_t 是一个时间限制,当这个时间到时,会执行后续的任务。就想上面的代码,实现是6.那么我们吧这个时限改成3会有什么不同呢。
  dispatch_semaphore_t objc=dispatch_semaphore_create(1);
    dispatch_time_t time=dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_wait(objc, time);
        NSLog(@"线程1开始");
        sleep(5);
        NSLog(@"线程1结束");
        dispatch_semaphore_signal(objc);
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
       sleep(1);
        dispatch_semaphore_wait(objc, time);
        NSLog(@"线程2开始");
        sleep(1);
        NSLog(@"线程2结束");
        dispatch_semaphore_signal(objc);
        
    });
2020-12-25 14:48:07.072062+0800 LBDaySurgery[19337:8005146] 线程1开始
2020-12-25 14:48:10.077145+0800 LBDaySurgery[19337:8005149] 线程2开始
2020-12-25 14:48:11.082572+0800 LBDaySurgery[19337:8005149] 线程2结束
2020-12-25 14:48:12.077257+0800 LBDaySurgery[19337:8005146] 线程1结束

限制性线程1,当在3秒的时候,达到限时时间,这个时候发送信号执行线程2.线程2执行完成后在继续执行1。

四.OSSpinLock

OSSpinLock 是一种自旋锁,和互斥锁类似,都是为了保证线程安全的锁。但二者的区别是不一样的,对于互斥锁,当一个线程获得这个锁之后,其他想要获得此锁的线程将会被阻塞,直到该锁被释放。但自选锁不一样,当一个线程获得锁之后,其他线程将会一直循环在哪里查看是否该锁被释放。所以,此锁比较适用于锁的持有者保存时间较短的情况下。

只有加锁,解锁,尝试加锁三个方法。
这个方法在ios10之后已经被废掉了。

五. os_unfair_lock

os_unfair_lock用法和OSSpinLock是一样的。
如下:

 //初始化
    os_unfair_lock unfairlock=OS_UNFAIR_LOCK_INIT;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        os_unfair_lock_lock(&unfairlock);
        NSLog(@"线程1开始");
        sleep(3);
        NSLog(@"线程1结束");
        os_unfair_lock_unlock(&unfairlock);
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          os_unfair_lock_lock(&unfairlock);
         sleep(1);
         NSLog(@"线程2");
        os_unfair_lock_unlock(&unfairlock);
         
     });
2020-12-25 16:09:48.083746+0800 LBDaySurgery[19393:8036711] 线程1开始
2020-12-25 16:09:49.086709+0800 LBDaySurgery[19393:8036715] 线程2
2020-12-25 16:09:51.088952+0800 LBDaySurgery[19393:8036711] 线程1结束
六. pthread_mutex 跨平台的锁 ,互斥锁
普通锁

这个应该对应上面说的NSLock,它们的用法是类似的。

 __block pthread_mutex_t objc;
    pthread_mutex_init(&objc, NULL);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
           pthread_mutex_lock(&objc);
           NSLog(@"线程1开始");
           sleep(3);
           NSLog(@"线程1结束");
           pthread_mutex_unlock(&objc);
           
       });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            sleep(1);
            pthread_mutex_lock(&objc);
            NSLog(@"线程2");
            pthread_mutex_unlock(&objc);
        });
2020-12-25 16:30:37.002505+0800 LBDaySurgery[19409:8043800] 线程1开始
2020-12-25 16:30:40.007640+0800 LBDaySurgery[19409:8043800] 线程1结束
2020-12-25 16:30:40.008098+0800 LBDaySurgery[19409:8043801] 线程2

其他的关于pthread_mutex的条件锁和递归锁就不一一展示用法了,后续就补充吧。

那么我没来总结下:
1.每一种锁基本上都是加锁、等待、解锁的步骤,理解了这三个步骤就可以帮你快速的学会各种锁的用法。
2.@synchronized 的效率最低,不过它的确用起来最方便,所以如果没什么性能瓶颈的话,可以选择使用 @synchronized。
3.当性能要求较高时候,可以使用 pthread_mutex 或者 dispath_semaphore,由于 OSSpinLock 不能很好的保证线程安全,而在只有在 iOS10 中才有 os_unfair_lock ,所以,前两个是比较好的选择。既可以保证速度,又可以保证线程安全。
4.对于 NSLock 及其子类,速度来说 NSLock < NSCondition < NSRecursiveLock < NSConditionLock 。

相关文章

  • iOS-线程锁

    总有人会问线程安全问题,自己平常在项目中也没怎么遇到,没办法了,自能自己上网查找,最后总结了。 线程锁用来干什么的...

  • iOS-多线程-锁

    多线程需要一种互斥的机制来访问共享资源。 一、 互斥锁 互斥锁的意思是某一时刻只允许一个线程访问某一资源。为了保证...

  • 4.0.6.守护线程,线程死锁

    守护线程会随着主线程的结束而结束DaemonThread 线程 1, 线程 2,锁1,锁2 线程1 有锁1,想拿锁...

  • iOS底层原理——浅谈RunLoop

    RunLoop应用:线程保活 线程保活、控制销毁 iOS-浅谈RunLoop8iOS底层原理总结 - RunLoo...

  • 悲观锁:一个线程得到锁,其它线程挂起,synchronized 乐观锁:一个线程得到锁,其它线程不断重试, cas...

  • sleep,wait, join yield

    锁池:所有需要竞争同步锁的线程都会放在锁池中,当一个线程得到锁后,其他线程都会在锁池中等待,当线程释放锁之后,其他...

  • 并发编程-线程

    线程 GIL 守护线程 线程锁(互斥锁 and 递归锁) 信号量 事件 条件 定时器 1.线程: 特点在多线程的操...

  • 深入理解AQS(二)- 共享模式

    共享锁与独占锁 独占锁被某个线程持有时,其他线程只能等待当前线程释放后才能去竞争锁,而且只有一个线程能竞争锁成功。...

  • iOS中各种锁的性能对比

    自旋锁 与 互斥锁 自旋锁 (spin lock): 如果一个线程需要获取自旋锁,该锁已经被其他线程占用,该线程不...

  • 死锁

    什么是死锁 简单的说:线程1持有A锁,线程2持有B锁;线程1尝试获取B锁,线程2尝试获取A锁。两个线程各持有了一把...

网友评论

    本文标题:iOS-线程锁

    本文链接:https://www.haomeiwen.com/subject/whcunktx.html