线程锁

2019-07-05  本文已影响0人  鐧箪

多线程编程中,应该尽量避免资源在线程之间共享,以减少线程间的相互作用。 但是总是有多个线程相互干扰的情况(如多个线程访问一个资源)。在线程必须交互的情况下,就需要一些同步工具,来确保当它们交互的时候是安全的。

我们在声明属性的时候 一个名词用的很频繁 atomic(原子性操作) 、 nonatomic(非原子性操作)
atomic 提供多线程安全 会有加锁操作 平时的操作因为不会涉及到多线程资源争夺 所以 用atomic 显的有些浪费 所以我们都用nonatomic 没有任何限制

下面我们来看实例吧

dispatch_queue_t queue = dispatch_queue_create("bf", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"线程1 ----------%@",[NSThread currentThread]);
        self.price = @"1000";
        NSLog(@"你好我要买一张机票 请问现在杭州到北京的票现在多少钱一张");
        NSLog(@"你好 现在杭州到北京的票价%@",self.price);
        NSLog(@"好的帮我预订一张");
        NSLog(@"好的先生我现在帮你处理订单");
        sleep(2);
        NSLog(@"操作好了请用支付宝或微信支付");
        NSLog(@"您已支付完毕 您购买的机票金额为%@元",self.price);
    });
    
    
    dispatch_async(queue, ^{
        NSLog(@"线程2 ----------%@",[NSThread currentThread]);
        sleep(2);
        self.price = @"1200";
    });
线程1 ----------<NSThread: 0x600001fe55c0>{number = 4, name = (null)}
线程2 ----------<NSThread: 0x600001fdaa00>{number = 3, name = (null)}
你好我要买一张机票 请问现在杭州到北京的票现在多少钱一张
你好 现在杭州到北京的票价1000
好的帮我预订一张
好的先生我现在帮你处理订单
操作好了请用支付宝或微信支付
您已支付完毕 您购买的机票金额为1200元

平时我们买飞机票 可以通过不同途径购买 飞机票的价格也是随着剩余的票数 决定打折程度 上面发送的问题是 票价为1000的机票 实际支付完成的时候确实1200

通过代码我们发现 异步并发的两个子线程 都是操作price 价格 导致了 一个线程在使用price 的时候发生价格变动 这显然是违背我们的主观意愿的 为了防止这种变动 我们需要用到线程锁来处理

iOS的线程锁有很多种 接下来我们来逐一介绍

@synchronized
@synchronized (self) {
        //需要加锁的写在里面
    }

synchronized中传入的object的内存地址,被用作key,通过hash map对应的一个系统维护的递归锁。不管是传入什么类型的object,只要是有内存地址,就能启动同步代码块的效果

@synchronized是最方便的 但是它是最慢的

dispatch_queue_t queue = dispatch_queue_create("bf", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"线程1 ----------%@",[NSThread currentThread]);
        @synchronized (self) {
            self.price = @"1000";
            NSLog(@"你好我要买一张机票 请问现在杭州到北京的票现在多少钱一张");
            NSLog(@"你好 现在杭州到北京的票价%@",self.price);
            NSLog(@"好的帮我预订一张");
            NSLog(@"好的先生我现在帮你处理订单");
            sleep(2);
            NSLog(@"操作好了请用支付宝或微信支付");
            NSLog(@"您已支付完毕 您购买的机票金额为%@元",self.price);
        }
    });

dispatch_async(queue, ^{
        NSLog(@"线程2 ----------%@",[NSThread currentThread]);
       //保证线程2 在线程1后加锁
        sleep(2);
        @synchronized (self) {
            self.price = @"1200";
            NSLog(@"当前杭州到北京的最新价格为%@",self.price);
        }
    });
你好我要买一张机票 请问现在杭州到北京的票现在多少钱一张
你好 现在杭州到北京的票价1000
好的帮我预订一张
好的先生我现在帮你处理订单
操作好了请用支付宝或微信支付
您已支付完毕 您购买的机票金额为1000元
当前杭州到北京的最新价格为1200
NSLock

遵循 NSLocking 协议 所以具备 lock(加锁) unlock(解锁) 两个要成对使用

lock 会阻塞当前线程 直到加锁成功

dispatch_async(queue, ^{
        NSLog(@"线程1 ----------%@",[NSThread currentThread]);
        [lock lock];
        self.price = @"1000";
        NSLog(@"你好我要买一张机票 请问现在杭州到北京的票现在多少钱一张");
        NSLog(@"你好 现在杭州到北京的票价%@",self.price);
        NSLog(@"好的帮我预订一张");
        NSLog(@"好的先生我现在帮你处理订单");
        sleep(2);
        NSLog(@"操作好了请用支付宝或微信支付");
        NSLog(@"您已支付完毕 您购买的机票金额为%@元",self.price);
        [lock unlock];
    });

dispatch_async(queue, ^{
        NSLog(@"线程2 ----------%@",[NSThread currentThread]);
        //保证线程2 在线程1后加锁
        sleep(2);
        [lock lock];
        self.price = @"1200";
        NSLog(@"当前杭州到北京的最新价格为%@",self.price);
        [lock unlock];
    });

尝试加锁 加锁成功返回YES 需要解锁 加锁失败 返回NO

- (BOOL)tryLock;
dispatch_async(queue, ^{
        NSLog(@"线程2 ----------%@",[NSThread currentThread]);
        sleep(2);
        if ([lock tryLock]) {
            self.price = @"1200";
            NSLog(@"当前杭州到北京的最新价格为%@",self.price);
        }else {
            NSLog(@"当前票价没变化");
        }
        
    });
当前票价没变化

输出 当前票价没变化之后方法执行完毕 没有尝试继续加锁 不会阻塞当前线程

在指定时间内尝试加锁

- (BOOL)lockBeforeDate:(NSDate *)limit;
dispatch_async(queue, ^{
        NSLog(@"线程2 ----------%@",[NSThread currentThread]);
        sleep(2);
        if ([lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]) {
            self.price = @"1200";
            NSLog(@"当前杭州到北京的最新价格为%@",self.price);
        }else {
            NSLog(@"当前票价没变化");
        }
        
    });
当前杭州到北京的最新价格为1200

会阻塞当前线程 并且在指定时间内 反复尝试加锁

NSConditionLock

条件锁 多个condition

//初始化 条件为0
    NSConditionLock * lock = [[NSConditionLock alloc] initWithCondition:50];
    
    dispatch_queue_t queue = dispatch_queue_create("bf", DISPATCH_QUEUE_CONCURRENT);
    
    
    dispatch_async(queue, ^{
        NSLog(@"线程1 ----------%@",[NSThread currentThread]);
        NSLog(@"1号犯人越狱中");
        [lock lockWhenCondition:1];
        NSLog(@"1号犯人越狱成功");
        [lock unlock];
    
    });
    
    
    dispatch_async(queue, ^{
        NSLog(@"线程2 ----------%@",[NSThread currentThread]);
        NSLog(@"2号犯人越狱中");
        [self archiveKey:lock];
       
    });
    
    dispatch_async(queue, ^{
        NSLog(@"线程3 ----------%@",[NSThread currentThread]);
        NSLog(@"3号犯人越狱中");
        [lock lockWhenCondition:3];
        NSLog(@"3号犯人越狱成功");
        [lock unlockWithCondition:4];
        
    });
    
    dispatch_async(queue, ^{
        NSLog(@"线程4 ----------%@",[NSThread currentThread]);
        NSLog(@"4号犯人越狱中");
        [lock lockWhenCondition:4];
        NSLog(@"4号犯人越狱成功");
        [lock unlockWithCondition:1];
        
    });
-(void)archiveKey:(NSConditionLock *)lock {
    NSLog(@"2号犯人开始越狱");
    NSInteger r = arc4random_uniform(100);
    if ([lock tryLockWhenCondition:r]) {
        NSLog(@"2号犯人反复尝试后终于拿到了监狱的钥匙 越狱成功");
        [lock unlockWithCondition:3];
    }else {
        NSLog(@"2号犯人越狱失败");
        [self archiveKey:lock];
    }
}
2号犯人反复尝试后终于拿到了监狱的钥匙 越狱成功
3号犯人越狱成功
4号犯人越狱成功
1号犯人越狱成功

条件锁 condtion 初始化可以默认一个值 加锁 、解锁 成功 都可以给condtion指定一个值 加锁失败 、不设置condtion 不变

通过 condition 我们可以实现线程依赖

NSRecursiveLock

递归锁 当前线程可以反复上锁不会导致死锁

NSLock * lock = [[NSLock alloc] init];
    NSRecursiveLock * relock = [[NSRecursiveLock alloc] init];
    dispatch_queue_t queue = dispatch_queue_create("bf", DISPATCH_QUEUE_CONCURRENT);
    
    
    dispatch_async(queue, ^{
        NSLog(@"线程1 ----------%@",[NSThread currentThread]);
        [lock lock];
        [lock lock];
        NSLog(@"1号犯人越狱成功");
        [lock unlock];
        [lock unlock];
    });
    
    
    dispatch_async(queue, ^{
        NSLog(@"线程2 ----------%@",[NSThread currentThread]);
        [relock lock];
        [relock lock];
        NSLog(@"2号犯人越狱成功");
        [relock unlock];
        [relock unlock];
    });
    
    
    dispatch_async(queue, ^{
        NSLog(@"线程3 ----------%@",[NSThread currentThread]);
        sleep(5);
        [relock lock];
        NSLog(@"3号犯人越狱成功");
        [relock unlock];
    });
2号犯人越狱成功
3号犯人越狱成功

可以看到 NSLock 因为同一个线程上了2次锁 已经造成死锁 不会有任何输出
NSRecursiveLock 可以同线程多次上锁 如同引用计数 会记录上锁的次数 当解锁次数相同的时候才会释放掉 才不会影响其他线程上锁。

NSCondition

线程检测器 不需要轮询 直接可以让当前线程等待 不影响其他线程上锁

NSCondition  * condition = [[NSCondition alloc] init];
    dispatch_queue_t queue = dispatch_queue_create("bf", DISPATCH_QUEUE_CONCURRENT);
    __block BOOL finished = NO;
    dispatch_async(queue, ^{
        [condition lock];
        
        if (!finished) {
            [condition wait];
        }
        
        NSLog(@"1号任务执行完毕");
        [condition unlock];
        
        
    });
    
    
    dispatch_async(queue, ^{
        sleep(2);
        [condition lock];
        sleep(10);
        NSLog(@"2号任务执行完毕");
        finished = YES;;
        [condition signal];
        [condition unlock];
        
    });  
2号任务执行完毕
1号任务执行完毕
OSSpinLock
typedef int32_t OSSpinLock OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock);

bool    OSSpinLockTry( volatile OSSpinLock *__lock );

void    OSSpinLockLock( volatile OSSpinLock *__lock );

void    OSSpinLockUnlock( volatile OSSpinLock *__lock );

自旋锁 同NSLock 不同的是 NSLock 轮询过后 会进入waiting 等待唤醒 OSSpinLock 会一直轮询 不会进入waiting 比较耗资源 但是速度快

__block OSSpinLock spinLock  = OS_SPINLOCK_INIT;
    dispatch_queue_t queue = dispatch_queue_create("bf", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"线程1 ----------%@",[NSThread currentThread]);
        OSSpinLockLock(&spinLock);
        self.price = @"1000";
        NSLog(@"你好我要买一张机票 请问现在杭州到北京的票现在多少钱一张");
        NSLog(@"你好 现在杭州到北京的票价%@",self.price);
        NSLog(@"好的帮我预订一张");
        NSLog(@"好的先生我现在帮你处理订单");
        sleep(10);
        NSLog(@"操作好了请用支付宝或微信支付");
        NSLog(@"您已支付完毕 您购买的机票金额为%@元",self.price);
        OSSpinLockUnlock(&spinLock);
    });
    
    
    dispatch_async(queue, ^{
        NSLog(@"线程2 ----------%@",[NSThread currentThread]);
        sleep(2);
        OSSpinLockLock(&spinLock);
        self.price = @"1200";
        NSLog(@"当前杭州到北京的最新价格为%@",self.price);
        OSSpinLockUnlock(&spinLock);
    });

效果同上

'OSSpinLock' is deprecated: first deprecated in iOS 10.0 - Use os_unfair_lock() from <os/lock.h> instead

你会发现 会有一个警告 因为OSSpinLock 有BUG存在 不再安全 所以推荐使用 os_unfair_lock

 __block os_unfair_lock unfairLock = OS_UNFAIR_LOCK_INIT;
 
    dispatch_queue_t queue = dispatch_queue_create("bf", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"线程1 ----------%@",[NSThread currentThread]);
        os_unfair_lock_lock(&unfairLock);
        self.price = @"1000";
        NSLog(@"你好我要买一张机票 请问现在杭州到北京的票现在多少钱一张");
        NSLog(@"你好 现在杭州到北京的票价%@",self.price);
        NSLog(@"好的帮我预订一张");
        NSLog(@"好的先生我现在帮你处理订单");
        sleep(10);
        NSLog(@"操作好了请用支付宝或微信支付");
        NSLog(@"您已支付完毕 您购买的机票金额为%@元",self.price);
        os_unfair_lock_unlock(&unfairLock);
    });
    
    
    dispatch_async(queue, ^{
        NSLog(@"线程2 ----------%@",[NSThread currentThread]);
        sleep(2);
        os_unfair_lock_lock(&unfairLock);
        self.price = @"1200";
        NSLog(@"当前杭州到北京的最新价格为%@",self.price);
        os_unfair_lock_unlock(&unfairLock);
     
    });
pthread_mutex_t

pthread_mutex_t 可以创建互斥锁 检错锁 递归锁

int pthread_mutex_init(pthread_mutex_t * __restrict,
        const pthread_mutexattr_t * _Nullable __restrict)

pthread_mutexattr_t 可以指定锁的类型 默认为互斥锁可以传NULL

#define PTHREAD_MUTEX_NORMAL        0   
#define PTHREAD_MUTEX_ERRORCHECK    1
#define PTHREAD_MUTEX_RECURSIVE     2
#define PTHREAD_MUTEX_DEFAULT       PTHREAD_MUTEX_NORMAL

PTHREAD_MUTEX_NORMAL 、PTHREAD_MUTEX_DEFAULT 默认为互斥锁 保证一次只有一个线程在执行 如果多次锁 会造成死锁

PTHREAD_MUTEX_ERRORCHECK 检错锁 同一个线程 加锁 第一次成功返回0 如果不解锁 后面再加锁则返回非0 不会造成死锁

PTHREAD_MUTEX_RECURSIVE 递归锁 同一个线程可以反复加锁 不会形成死锁 当然要全部解锁掉 才不会影像其他线程

static pthread_mutex_t mutex_lock;  
pthread_mutexattr_t attr;
    pthread_mutexattr_init (&attr);
    pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_ERRORCHECK);
 pthread_mutex_init(&mutex_lock, &attr);
pthread_mutexattr_destroy (&attr);
    
    
    pthread_t thread;
    pthread_create(&thread, NULL, methord, NULL);
    
    pthread_t otherThread;
    pthread_create(&otherThread, NULL, otherMethord, NULL);

void * methord() {
    NSLog(@"线程1 ----------%@",[NSThread currentThread]);
    
   int a = pthread_mutex_lock(&mutex_lock);
   int b =  pthread_mutex_lock(&mutex_lock);
NSLog(@"b------%d",b);
    sleep(6);
    NSLog(@"1号开始活动");
    pthread_mutex_unlock(&mutex_lock);
    pthread_mutex_unlock(&mutex_lock);
    
    
    return 0;
}

void * otherMethord() {
    NSLog(@"线程2 ----------%@",[NSThread currentThread]);
    sleep(2);
    pthread_mutex_lock(&mutex_lock);
    NSLog(@"2号开始活动");
    pthread_mutex_unlock(&mutex_lock);
    
    return 0;
}

线程3 ----------<NSThread: 0x6000035bf5c0>{number = 4, name = (null)}
线程2 ----------<NSThread: 0x60000358cb00>{number = 5, name = (null)}
线程1 ----------<NSThread: 0x6000035bf380>{number = 3, name = (null)}
b------11
1号开始活动
2号开始活动
上一篇 下一篇

猜你喜欢

热点阅读