iOS关于线程加锁方案

从前的锁也好看
钥匙精美有样子
你锁了 人家就懂了

一、存在的问题

1、实例

@interface TSDController ()

@property(nonatomic, assign) int totalMoney;

@end

@implementation TSDController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.totalMoney = 200;
    [self earnAndCostMoney];
    // Do any additional setup after loading the view.
}
- (void)earnAndCostMoney {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     
     dispatch_async(queue, ^{
         for (int i = 0; i < 5; i++) {
             [self earnMoney];
         }
     });
     
     dispatch_async(queue, ^{
         for (int i = 0; i < 5; i++) {
             [self costMoney];
         }
     });
}
- (void)earnMoney {
    int oldMoney = self.totalMoney;
    sleep(0.3);
    oldMoney += 40;
    self.totalMoney = oldMoney;
    NSLog(@"挣40元,余额%d元---%@", self.totalMoney, [NSThread currentThread]);
}
- (void)costMoney {
    int oldMoney = self.totalMoney;
    sleep(0.3);
    oldMoney -= 20;
    self.totalMoney = oldMoney;
    NSLog(@"花20元,余额%d元---%@", self.totalMoney, [NSThread currentThread]);
}

打印结果(最后余额应该是300的,出问题的结果不一定,此次是320):

花20元,余额180元---<NSThread: 0x60000002ff40>{number = 5, name = (null)}
挣40元,余额220元---<NSThread: 0x60000005e680>{number = 6, name = (null)}
花20元,余额200元---<NSThread: 0x60000002ff40>{number = 5, name = (null)}
花20元,余额180元---<NSThread: 0x60000002ff40>{number = 5, name = (null)}
挣40元,余额240元---<NSThread: 0x60000005e680>{number = 6, name = (null)}
挣40元,余额260元---<NSThread: 0x60000005e680>{number = 6, name = (null)}
花20元,余额220元---<NSThread: 0x60000002ff40>{number = 5, name = (null)}
挣40元,余额300元---<NSThread: 0x60000005e680>{number = 6, name = (null)}
花20元,余额280元---<NSThread: 0x60000002ff40>{number = 5, name = (null)}
挣40元,余额320元---<NSThread: 0x60000005e680>{number = 6, name = (null)}

2、图解

1、存在问题的结果.png

2、我们希望的结果.png

由上图以及代码可知,在多个线程同时对同一个对象做处理的时候,有可能出现,多个任务在同一时间,没有先后顺序的对同一个对象进行读或写的操作,这样的话,就会出现上面的问题,而解决这一问题的办法就是线程同步方案(线程锁)

二、线程同步方案(线程锁)

1、OSSPinLock: 自旋锁, 性能很高,但是不推荐使用.

导入头文件:#import <libkern/OSAtomic.h>
创建锁:
@property (assign, nonatomic) OSSpinLock lock;
// 初始化锁
self.lock = OS_SPINLOCK_INIT;
加锁:OSSpinLockLock(&_lock);
添加关键代码<><><>
解锁:OSSPinLockUnLock(&_lock)
注意: lock 要做成 全局变量,要使用同一锁才可以。如果有两个方法:判断两个方法 是否能同时执行(读写操作),如果需要这两个方法不能同时执行(读写操作) 则需要共用一把锁。
原理:第二条线程 会等待 (此时会存在线程等待)第一条线程解锁,才会继续执行。
p.s.OSSpinLock 的线程等待会处于忙等状态 (while(1);)会一直占有CPU 的资源 并没有睡觉 休息。 目前此 锁已经不安全了 会出现问题:线程优先级反转问题。ios10 以后使用会警告️。

- (void)earnMoney {
    OSSpinLockLock(&_lock);
    int oldMoney = self.totalMoney;
    sleep(0.3);
    oldMoney += 40;
    self.totalMoney = oldMoney;
    NSLog(@"挣40元,余额%d元---%@", self.totalMoney, [NSThread currentThread]);
    OSSpinLockUnlock(&_lock);
}
- (void)costMoney {
    OSSpinLockLock(&_lock);
    int oldMoney = self.totalMoney;
    sleep(0.3);
    oldMoney -= 20;
    self.totalMoney = oldMoney;
    NSLog(@"花20元,余额%d元---%@", self.totalMoney, [NSThread currentThread]);
    OSSpinLockUnlock(&_lock);
}

打印结果:

挣40元,余额240元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
挣40元,余额280元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
挣40元,余额320元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
挣40元,余额360元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
挣40元,余额400元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
花20元,余额380元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
花20元,余额360元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
花20元,余额340元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
花20元,余额320元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}
花20元,余额300元---<NSThread: 0x60000307a0c0>{number = 6, name = (null)}

2. os_unfair_lock 现在它代替 OSSpinLock iOS 10 以后系统使用

导入头文件:import<os/lcok.h>
注意:os_unfair_lock 是一个C语言的结构体,如果要使用属性调用需要使用assign修饰,从iOS10开始支持,用于取代不安全的OSSpinLock
// Low-level lock的特点等不到锁就休眠
@property (assign, nonatomic) os_unfair_lock moneyLock;
使用方法:初始化、加锁、解锁
写法

- (void)earnMoney {
    os_unfair_lock_lock(&_lock);
    int oldMoney = self.totalMoney;
    sleep(0.3);
    oldMoney += 40;
    self.totalMoney = oldMoney;
    NSLog(@"挣40元,余额%d元---%@", self.totalMoney, [NSThread currentThread]);
    os_unfair_lock_unlock(&_lock);
}
- (void)costMoney {
    os_unfair_lock_lock(&_lock);
    int oldMoney = self.totalMoney;
    sleep(0.3);
    oldMoney -= 20;
    self.totalMoney = oldMoney;
    NSLog(@"花20元,余额%d元---%@", self.totalMoney, [NSThread currentThread]);
    os_unfair_lock_unlock(&_lock);
}

打印log和OSSPinLock一样,注意:1、如果忘记解锁,os_unfair_lock的线程会一直处于等待,出现线程死锁现象。2、如果iOS 10 以下版本使用,会出现闪退现象

3、pthread_mutex :多平台通用,跨平台:互斥锁。等待锁的线程或处于休眠状态。区别于 自旋锁(OSSpinLock),自旋锁不会使线程休眠.

使用方式:导入头文件:#import<pthread.h>
静态初始化:pthread_mutex mutex = PTHREAD_MUTEX_INITIALIZER
但是不能直接 赋值给类属性,因为 PTHREAD_MUTEX_INITIALIZER 是一个结构体。
当设置type为 PTHREAD_MUTEX_NORMAL为互斥锁,当设置为PTHREAD_MUTEX_RECURSIVE时,为递归锁
@property (assign, nonatomic) pthread_mutex_t mutex;
动态初始化方式:

互斥锁

       // 初始化属性
       pthread_mutexattr_t attr;
       pthread_mutexattr_init(&attr);
       pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); // normal
       // 初始化锁
       pthread_mutex_init(&_mutex, &attr);
       // 销毁属性
       pthread_mutexattr_destroy(&attr);

注意: 这里的属性设置pthread_mutex_init(&ticketMutex, NULL); 可以传NULL,默认就等同于:PTHREAD_MUTEX_DEFAULT

- (void)earnMoney {
    pthread_mutex_lock(&_mutex);
    int oldMoney = self.totalMoney;
    sleep(0.3);
    oldMoney += 40;
    self.totalMoney = oldMoney;
    NSLog(@"挣40元,余额%d元---%@", self.totalMoney, [NSThread currentThread]);
    pthread_mutex_unlock(&_mutex);
}
- (void)costMoney {
    pthread_mutex_lock(&_mutex);
    int oldMoney = self.totalMoney;
    sleep(0.3);
    oldMoney -= 20;
    self.totalMoney = oldMoney;
    NSLog(@"花20元,余额%d元---%@", self.totalMoney, [NSThread currentThread]);
    pthread_mutex_unlock(&_mutex);
}

最后注意把mutex 销毁掉

- (void)dealloc
{
    pthread_mutex_destroy(& _mutex);
}

递归锁

pthread_mutex的一种,只需要改变 mutex的属性即可: 修改为:PTHREAD_MUTEX_RECURSIVE
注意

1>如果递归调用的方法里有用到锁,那么就必须用递归锁,否则会出现死锁
2>同一个线程可以对递归锁进行重复上锁

比如:

- (void)viewDidLoad {
    [super viewDidLoad];

    // 初始化属性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // recursive
    // 初始化锁
    pthread_mutex_init(&_mutex, &attr);
    // 销毁属性
    pthread_mutexattr_destroy(&attr);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self recursive];
    });
}

- (void)recursive {
    // 将_mutex锁上
    pthread_mutex_lock(&_mutex);
    static int count = 0;
    count++;
    if (count <= 5) {
        NSLog(@"%s---%@", __func__, [NSThread currentThread]);
        // 递归
        [self recursive];
    }
    
    // 将_mutex打开
    pthread_mutex_unlock(&_mutex);
}

// 打印
-[ViewController recursive]---<NSThread: 0x6000004735c0>{number = 5, name = (null)}
-[ViewController recursive]---<NSThread: 0x6000004735c0>{number = 5, name = (null)}
-[ViewController recursive]---<NSThread: 0x6000004735c0>{number = 5, name = (null)}
-[ViewController recursive]---<NSThread: 0x6000004735c0>{number = 5, name = (null)}
-[ViewController recursive]---<NSThread: 0x6000004735c0>{number = 5, name = (null)}

pthread_cond(条件锁)

  • 使用
@interface ViewController ()
@property (nonatomic, assign) pthread_cond_t cond;
@property (nonatomic, strong) NSMutableArray *mArray;
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    // 初始化锁
    pthread_mutex_init(&_mutex, NULL);
    // 初始化条件
    pthread_cond_init(&_cond, NULL);
    [self condition];
}

- (void)condition {
    self.mArray = [NSMutableArray new];
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);    dispatch_async(queue, ^{
        sleep(1.0);
        [self add];
    });
    dispatch_async(queue, ^{
        [self remove];
    });
}
- (void)add {
    // 将_mutex锁上
    pthread_mutex_lock(&_mutex);
    [self.mArray addObject:@"1"];
    NSLog(@"%s---%@", __func__, [NSThread currentThread]);
    // 发送信号
    pthread_cond_signal(&_cond);
    // 将_mutex打开
    pthread_mutex_unlock(&_mutex);
}

- (void)remove {
    // 将_mutex锁上
    pthread_mutex_lock(&_mutex);
    if (self.mArray.count == 0) {
        // 等待信号
        pthread_cond_wait(&_cond, &_mutex);
    }
    [self.mArray removeLastObject];
    NSLog(@"%s---%@", __func__, [NSThread currentThread]);
    // 将_mutex打开
    pthread_mutex_unlock(&_mutex);
}
- (void)dealloc {
    // 销毁锁
    pthread_mutex_destroy(&_mutex);
    // 销毁条件
    pthread_cond_destroy(&_cond);
}

// 打印
-[ViewController add]---<NSThread: 0x6000005e3980>{number = 5, name = (null)}
-[ViewController remove]---<NSThread: 0x6000005944c0>{number = 7, name = (null)}

以上如果不加条件信号pthread_cond_signal和等待信号的话pthread_cond_wait,那么打印输出的控制台log的次序会正好相反

  • 说明

1>某个线程中的操作在某种情况下需要依赖另外一个线程中的操作,那么就可以用条件来实现
2>pthread_cond_wait函数会让线程进入休眠状态,并且将锁打开
3>当收到信号时,pthread_cond_wait函数会唤醒线程,并且将锁再次锁上

4、NSLock

它是对pthread_mutex(normal)的封装(互斥锁)
使用:

@interface ViewController ()
@property (nonatomic, strong) NSLock *lock;
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 初始化锁
    self.lock = [NSLock new];
    [self earnAndCostMoney];
}

- (void) earnMoney {
    // 将lock锁上
    [self.lock lock];
    int oldMoney = self.totalMoney;
    sleep(0.3);
    oldMoney += 40;
    self.totalMoney = oldMoney;
    NSLog(@"挣40元,余额%d元---%@", self.totalMoney, [NSThread currentThread]);
    // 将lock打开
    [self.lock unlock];
}

- (void) costMoney {
    // 将lock锁上
    [self.lock lock];
    int oldMoney = self.totalMoney;
    sleep(0.3);
    oldMoney -= 20;
    self.totalMoney = oldMoney;
    NSLog(@"花20元,余额%d元---%@", self.totalMoney, [NSThread currentThread]);
    // 将lock打开
    [self.lock unlock];
}

5、NSRecursiveLock

它是对pthread_mutex(recursive)的封装
使用:

@interface ViewController ()
@property (nonatomic, strong) NSRecursiveLock *lock;
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 初始化锁
    self.lock = [NSRecursiveLock new];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self recursive];
    });
}

- (void)recursive {
    // 将lock锁上
    [self.lock lock];
    
    static int count = 0;
    count++;
    if (count <= 5) {
        NSLog(@"%s---%@", __func__, [NSThread currentThread]);
        // 递归
        [self recursive];
    }
    
    // 将lock打开
    [self.lock unlock];
}

6、NSCondition

它是对pthread_mutexpthread_cond的封装
使用:

@interface ViewController ()
@property (nonatomic, strong) NSCondition *lock;
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 初始化锁
    self.lock = [NSCondition new];
    [self condition];
}

- (void)add {
    // 将lock锁上
    [self.lock lock];

    [self.mArray addObject:@"1"];
    NSLog(@"%s---%@", __func__, [NSThread currentThread]);
    // 发送信号
    [self.lock signal];

    // 将lock打开
    [self.lock unlock];
}

- (void)remove {
    // 将lock锁上
    [self.lock lock];

    if (self.mArray.count == 0) {
        // 等待信号
        [self.lock wait];
    }
    [self.mArray removeLastObject];
    NSLog(@"%s---%@", __func__, [NSThread currentThread]);

    // 将lock打开
    [self.lock unlock];
}

7、NSConditionLock

它是对NSCondition的封装,可以利用条件值来控制线程的执行顺序

@interface ViewController ()
@property (nonatomic, strong) NSConditionLock *lock;
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 条件值初始化为1
    self.lock = [[NSConditionLock alloc] initWithCondition:1];
    [self condition];
}

- (void)condition {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(queue, ^{
        [self one];
    });

    dispatch_async(queue, ^{
        [self two];
    });
    
    dispatch_async(queue, ^{
        [self three];
    });
}

- (void)one {
    // 条件值为3往下执行,否则等待
    [self.lock lockWhenCondition:3];

    NSLog(@"%s---%@", __func__, [NSThread currentThread]);

    // 不设置条件值
    [self.lock unlock];
    
    NSLog(@"当前条件值---%zd", self.lock.condition);
}

- (void)two {
    // 条件值为2往下执行,否则等待
    [self.lock lockWhenCondition:2];

    NSLog(@"%s---%@", __func__, [NSThread currentThread]);

    // 条件值设置为3
    [self.lock unlockWithCondition:3];
    
    NSLog(@"当前条件值---%zd", self.lock.condition);
}

- (void)three {
    // 条件值为1往下执行,否则等待
    [self.lock lockWhenCondition:1];

    NSLog(@"%s---%@", __func__, [NSThread currentThread]);

    // 条件值设置为2
    [self.lock unlockWithCondition:2];
    
    NSLog(@"当前条件值---%zd", self.lock.condition);
}

// 打印
-[ViewController three]---<NSThread: 0x600002ae7400>{number = 4, name = (null)}
当前条件值---2
-[ViewController two]---<NSThread: 0x600002ae2680>{number = 6, name = (null)}
当前条件值---3
-[ViewController one]---<NSThread: 0x600002adfa80>{number = 7, name = (null)}
当前条件值---3

参考链接:iOS多线程:十种线程锁, iOS多线程及线程同步方案(线程锁)总结

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

推荐阅读更多精彩内容