ios之各种锁机制的使用和理解

锁可以分为两大类:自旋锁(OSSpinLock)和互斥锁(pthread_mutex)。

相同点:
都能保证同一时间只有一个线程访问共享资源。都能保证线程安全。

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

各种锁性能:
除了 OSSpinLock 外,dispatch_semaphore 和 pthread_mutex 性能是最高 @synchronized() 是性能最低的

OSSpinLock已经废除就不做介绍了
下面是常见的几种锁:

@synchronized()

互斥锁@synchronized (obj):obj为该锁的唯一标识,标识相同则是同一把锁。当线程里执行obj锁{}代码块时 相当于获得了obj锁 这时其他线程就不能获得obj锁
只有当{}代码块执行完释放了obj锁 其他线程才能获得这个锁 并执行{}中锁住的代码

@synchronized锁 里套 @synchronized锁 是不会死锁的 因为@synchronized底层实现使用的就是递归锁(NSRecursiveLock)

#pragma mark - @synchronized的互斥
- (void)twoSynchronized{
    /*  @synchronized(obj)指令使用的obj为该锁的唯一标识,只有当标识相同时,才为满足互斥 
下面两个@synchronized锁的标识一样 是同一把锁 当第一个线程获得锁后 锁里面的代码执行完后才释放锁 
第二个线程才能获取到锁 才能继续执行锁里面的内容  所以执行结果是:
     需要线程同步的操作1 开始
     需要线程同步的操作1 结束
     需要线程同步的操作2
     */
    NSObject *obj = [[NSObject alloc] init];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        @synchronized(obj) {
            NSLog(@"需要线程同步的操作1 开始");
            sleep(3);
            NSLog(@"需要线程同步的操作1 结束");
        }
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        @synchronized(obj) {
            NSLog(@"需要线程同步的操作2");
        }
    });
}
NSLock 互斥锁

NSLock 遵循 NSLocking 协议,lock() unlock()方法是NSLocking协议的方法,就不说了 ,一个加锁一个解锁 总是成对出现 .
NSLock类还增加了- (BOOL)tryLock;和- (BOOL)lockBeforeDate:(NSDate *)limit;方法。我们来看下:

tryLock尝试获取一个锁,但是如果锁不可用的时候,它不会阻塞线程,相反,它只是返回NO。如果获取到锁后要记得unlock解锁,这样其他线程才能获取锁 相当于[lock lockBeforeDate:[NSDate date]];
lockBeforeDate:方法试图获取一个锁,但是如果锁没有在规定的时间内被获得,它会让线程从阻塞状态变为非阻塞状态(或者返回NO)。会等待时间 如果时间到了还没有获取到锁则返回NO 如果获取到了就执行下面语句 没获取到 且时间还没到就会等待着 也就是线程阻塞 不会执行后面的代码

看一段代码

    NSLock *lock = [[NSLock alloc] init];
    // 线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [lock lockBeforeDate:[NSDate date]];
        NSLog(@"需要线程同步的操作1 开始");
        sleep(2);
        NSLog(@"需要线程同步的操作1 结束");
        [lock unlock];
    });
    // 线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1); //以保证让线程2的代码后执行
        if ([lock tryLock]) {//尝试获取锁,如果获取不到返回NO,不会阻塞该线程,如果获取到锁 记得unlock解锁
            NSLog(@"锁可用的操作");
            [lock unlock];
        }else{
            NSLog(@"锁不可用的操作");
        }
        NSDate *date = [[NSDate alloc] initWithTimeIntervalSinceNow:3];
        if ([lock lockBeforeDate:date]) {//尝试在未来的3s内获取锁,并阻塞该线程,如果3s内获取不到恢复线程, 返回NO,不再阻塞线程
            NSLog(@"没有超时,获得锁");
            [lock unlock];
        }else{
            NSLog(@"超时,没有获得锁");
        }
    });
    
    /**
     结果:
     需要线程同步的操作1 开始
     锁不可用的操作
     需要线程同步的操作1 结束
     没有超时,获得锁
     */

NSConditionLock -- 条件锁

@interface NSConditionLock : NSObject  {
@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 API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

我们发现条件condition是NSInteger类型的
当condition相等的时候才能获得锁 也就是加锁

需要注意的是- (void)unlockWithCondition:(NSInteger)condition;
这个方法有两个作用1、就是解锁 2、是改变锁的条件

看下面代码:

//主线程中
    NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:0];
    
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [lock lockWhenCondition:1];
        NSLog(@"线程1");
        sleep(2);
        [lock unlock];
    });
    
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);//以保证让线程2的代码后执行
        if ([lock tryLockWhenCondition:0]) {
            NSLog(@"线程2");
            [lock unlockWithCondition:2];
            NSLog(@"线程2解锁成功");
        } else {
            NSLog(@"线程2尝试加锁失败");
        }
    });
    
    //线程3
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(2);//以保证让线程2的代码后执行
        if ([lock tryLockWhenCondition:2]) {
            NSLog(@"线程3");
            [lock unlock];
            NSLog(@"线程3解锁成功");
        } else {
            NSLog(@"线程3尝试加锁失败");
        }
    });
    
    //线程4
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(3);//以保证让线程2的代码后执行
        if ([lock tryLockWhenCondition:2]) {
            NSLog(@"线程4");
            [lock unlockWithCondition:1];
            NSLog(@"线程4解锁成功");
        } else {
            NSLog(@"线程4尝试加锁失败");
        }
    });

2019-02-21 10:47:29.227968+0800 LJConditionLock[2191:320996] 线程2  (满足了条件,所以先执行了线程2 ,然后改变条件为2)
2019-02-21 10:47:29.228109+0800 LJConditionLock[2191:320996] 线程2解锁成功
2019-02-21 10:47:30.227654+0800 LJConditionLock[2191:320995] 线程3 (线程2改变条件为2 正好等于线程3的条件)
2019-02-21 10:47:30.227843+0800 LJConditionLock[2191:320995] 线程3解锁成功
2019-02-21 10:47:31.226466+0800 LJConditionLock[2191:320997] 线程4
2019-02-21 10:47:31.226634+0800 LJConditionLock[2191:320997] 线程4解锁成功
2019-02-21 10:47:31.226645+0800 LJConditionLock[2191:320994] 线程1

从结果看 NSConditionLock 还可以实现任务之间的依赖

NSRecursiveLock--递归锁

允许同一线程多次加锁,而不会造成死锁
看下面的代码:

NSLock *lock = [[NSLock alloc] init];
 
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
 
    static void (^RecursiveMethod)(int);
 
    RecursiveMethod = ^(int value) {
 
        [lock lock];
        if (value > 0) {
 
            NSLog(@"value = %d", value);
            sleep(2);
            // 递归调用
            RecursiveMethod(value - 1);
        }
        [lock unlock];
    };
 
    RecursiveMethod(5);
});

这段代码是一个典型的死锁情况。在我们的线程中,RecursiveMethod是递归调用的。所以每次进入这个block时,都会去加一次锁,而从第二次开始,由于锁已经被使用了且没有解锁,所以它需要等待锁被解除,这样就导致了死锁,线程被阻塞住了。
递归锁允许同一线程多次加锁,而不会造成死锁,就是用来解决这类递归或者循环加锁问题的

同样的NSRecursiveLock也有两个方法 :

  • (BOOL)tryLock;
  • (BOOL)lockBeforeDate:(NSDate *)limit;
    是跟NSLock一样的用法

pthread_mutex 互斥锁

和NSLock


#import 

#define Lock() pthread_mutex_lock(&_lock)
#define Unlock() pthread_mutex_unlock(&_lock)

@implementation LJpthread_mutexVC
{
    pthread_mutex_t _lock; // 声明
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // 初始化
    pthread_mutex_init(&_lock,NULL);
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
        Lock();
        NSLog(@"任务1开始");
        sleep(2);
        NSLog(@"任务1结束");
        Unlock();
    });
    
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
        sleep(1);
        Lock();
        NSLog(@"任务2");
        Unlock();
    });
}

1、申明一个互斥锁,pthread_mutex_t _lock;
2、初始化它,pthread_mutex_init(& _lock,NULL);
3、使用_lock之前一定要初始化,否则不生效
4、获得锁,pthread_mutex_lock(& _lock);
5、解锁,pthread_mutex_unlock(& _lock);

dispatch_semaphore信号量作为锁

/* dispatch_semaphore 只有三个方法
 dispatch_semaphore_create(long value); // 创建信号量 参数是信号数量 必须要大于等于0
 dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); // 等待获取信号 当前信号量大于等于1的时候 才能获得 获得之后信号量减1
 dispatch_semaphore_signal(dispatch_semaphore_t dsema); // 释放信号量 使信号量加1
 
 信号量相当于停车场 有n个车位 wait 相当于来了一辆车 如果有空闲车位 就停进去 空闲车位数减一,没有就等待 signal相当于开走一辆 空闲车位数加一
 在访问共同资源时,可以控制有几个线程来同时访问 当信号量为1时 就相当于锁的作用
*/
@interface LJSemaphoreVC ()
{
    dispatch_semaphore_t sem;
}
/** 售票员01 */
@property (nonatomic, strong) NSThread *thread01;
/** 售票员02 */
@property (nonatomic, strong) NSThread *thread02;
/** 售票员03 */
@property (nonatomic, strong) NSThread *thread03;
/** 票的总数 */
@property (nonatomic, assign) NSInteger ticketCount;
@end

@implementation LJSemaphoreVC

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    

    sem = dispatch_semaphore_create(1);
    self.ticketCount = 100;
    self.thread01 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread01.name = @"售票员01";
    
    self.thread02 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread02.name = @"售票员02";
    
    self.thread03 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread03.name = @"售票员03";
    
    [self.thread01 start];
    //    sleep(1);
    [self.thread02 start];
    [self.thread03 start];
}
- (void)saleTicket
{
    
    while (1) {
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 信号量减1 如果信号量 > 0 获得信号 执行下面代码 否则等待
            // 先取出总数
            NSInteger count = self.ticketCount;
            if (count > 0) {
                self.ticketCount = count - 1;
                NSLog(@"%@卖了一张票,还剩下%zd张", [NSThread currentThread].name, self.ticketCount);
            }else{
                NSLog(@"票卖完了");
                break; // 跳出循环
            }
        dispatch_semaphore_signal(sem); // 操作完成 信号量加1 释放信号
    }
}

还有读写锁pthread_rwlock、条件锁NSCondition等就不一一介绍了,主要是理解锁的机制和原理,能够知其所以然。

你可能感兴趣的:(ios之各种锁机制的使用和理解)