线程锁

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

我们在声明属性的时候 一个名词用的很频繁 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号开始活动
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容