iOS 锁 部分一

主要讲解OSSpinLock/os_unfair_lock/pthread_mutex_t锁的基本用法

常见锁的分类:

  • 自旋锁OSSpinLock
  • 互斥锁os_unfair_lock
  • 互斥/递归/条件锁pthread_mutex_t
  • 互斥锁NSLock
  • 递归锁NSRecursiveLock
  • 条件锁NSCondition
  • 条件锁NSConditionLock
  • 递归锁 @synchronized
  • 信号量semaphore
  • 读写锁pthread_rwlock_t
  • 异步栅栏dispatch_barrier_async

iOS 锁 部分一
iOS 锁 部分二
iOS 锁 部分三
iOS 锁 部分四


1. 自旋锁 OSSpinLock

特点:

  1. 不是一个安全的锁, 等待锁的线程会处于忙等(busy-wait)状态, 一直占用着CPU资源; (类似一个while(1)循环一样,不停的查询锁的状态, 注意区分Runloop的机制, 同样是阻塞, 但是Runloop是类似休眠的阻塞, 不会耗费CPU资源, 自旋锁的这种忙等机制使它相比其他锁效率更高, 因为没有唤醒-休眠这些类似操作, 从而能更快去处理事情);

注意OSSpinLock自旋锁iOS10之后已经废弃使用, 它可能会导致优先级反转; 例如A/B两个线程, A的优先级大于B的, 我们的本意是A的任务优先执行; 但是使用OSSpinLock后, 如果是B先获得了优先访问了共享资源获得了锁并加锁, 而A线程再去访问共享资源时锁就会处于忙等状态, 由于优先级高则它会一直占用CPU资源不会让出时间片;这样B一直不能获得CPU资源去执行任务, 导致无法完成;

  1. 使用时需要导入头文件#import <libkern/OSAtomic.h>
  2. 使用时可能会用到的方法:
    3.1 OS_SPINLOCK_INIT初始化自旋锁;
    3.2 OSSpinLockLock()加锁;
    3.3 OSSpinLockUnlock()解锁;
    3.4 OSSpinLockTry()尝试加锁, 返回一个BOOL值;
  3. 示例代码如下
#import "ViewController1.h"
#import <libkern/OSAtomic.h>
@interface ViewController1 ()
@property (nonatomic, assign) NSInteger money;
@property (nonatomic, assign) OSSpinLock lock;
@end

@implementation ViewController1
- (void)viewDidLoad {
    [super viewDidLoad];
    self.money = 100;
    self.lock = OS_SPINLOCK_INIT;
    __weak typeof(self) weakSelf = self;
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i ++) {
            [weakSelf saveMoney];
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i ++) {
            [weakSelf drawMoney];
        }
    });
}
- (void)saveMoney{
    //加锁
    OSSpinLockLock(&_lock);
    [super saveMoney];
    //解锁
    OSSpinLockUnlock(&_lock);
}
- (void)drawMoney {
    //加锁
    OSSpinLockLock(&_lock);
    [super drawMoney ]; 
    //解锁
    OSSpinLockUnlock(&_lock);
}
@end

2. 互斥锁 os_unfair_lock

特点

  1. 设计宗旨在于替换OSSpinLock, 从 iOS10之后开始支持; 跟OSSpinLock不同, 等待os_unfari_lock的线程会处于休眠状态(类似Runloop那这样), 不是忙等;
  2. 使用时需要引入头文件#import <os/lock.h>
  3. 使用时可能会用到的方法
    3.1 OS_UNFAIR_LOCK_INIT初始化锁;
    3.2 os_unfair_lock_lock()加锁;
    3.3 os_unfair_lock_unlock()解锁;
    3.4 os_unfair_lock_trylock()尝试加锁, 返回一个BOOL值;
  4. 测试代码
#import "ViewController2.h"
#import <os/lock.h>
@interface ViewController2 ()
@property (nonatomic, assign) NSInteger money;
@property (nonatomic, assign) os_unfair_lock  lock;
@end
@implementation ViewController2
- (void)viewDidLoad {
    [super viewDidLoad];
    self.money = 100;
    self.lock = OS_UNFAIR_LOCK_INIT;
    __weak typeof(self) weakSelf = self;
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i ++) {
            [weakSelf saveMoney];
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i ++) {
            [weakSelf drawMoney];
        }
    });
}
- (void)saveMoney{
    os_unfair_lock_lock(&_lock);
    [super saveMoney];
    os_unfair_lock_unlock(&_lock);
}
- (void)drawMoney {
    os_unfair_lock_lock(&_lock);
    [super drawMoney ];
    os_unfair_lock_unlock(&_lock);
}
@end

3. 互斥/递归/条件锁 pthread_mutex_t

特点

  1. 跨平台使用的API, 等待锁的线程会处于休眠状态;
    当使用递归锁时: 允许同一个线程重复进行加锁(另一个线程访问时就会等待), 这样可以保证多线程时访问共用资源的安全性;
  2. 使用时需要引入头文件#import <pthread.h>
  3. 使用时可能会用到的方法
    3.1 锁的类型有
    ///普通互斥锁
  #define PTHREAD_MUTEX_NORMAL      0
   ///错误检查锁(不清楚什么用)
  #define PTHREAD_MUTEX_ERRORCHECK  1
   ///递归锁
  #define PTHREAD_MUTEX_RECURSIVE       2
  ///等同PTHREAD_MUTEX_NORMAL
  #define PTHREAD_MUTEX_DEFAULT     PTHREAD_MUTEX_NORMAL

3.2 锁的初始化方法:

   ///初始化属性
   pthread_mutexattr_t att ;
   pthread_mutexattr_init(&att);
   ///设置锁的属性
   pthread_mutexattr_settype(&att, PTHREAD_MUTEX_DEFAULT);
   ///初始化锁
   pthread_mutex_init(&_lock, &att);

3.2 pthread_mutex_lock()加锁
3.3 pthread_mutex_unlock()解锁
3.4 pthread_mutexattr_destroy()销毁属性;
3.5 pthread_mutex_destroy()销毁锁;
3.6 pthread_cond_wait()等待条件(进入休眠的同时放开 mutex 锁, 被唤醒的同时再次对 mutex 加锁)
3.7 pthread_cond_signal()发送信号激活等待该条件的线程;
3.8 pthread_cond_broadcast()发送广播信号激活等待该条件的所有线程;

  1. 关于互斥锁递归锁的测试代码:
#import "ViewController3.h"
 #import <pthread.h>
@interface ViewController3 ()
@property (nonatomic, assign) NSInteger money;
@property (nonatomic, assign) pthread_mutex_t  lock;
@property (nonatomic, assign) pthread_mutex_t  recursiveLock;
@end
@implementation ViewController3
- (void)viewDidLoad {
    [super viewDidLoad];    
/******************************互斥锁验证******************************/
    self.money = 100;
        ///初始化属性
    pthread_mutexattr_t att ;
    pthread_mutexattr_init(&att);
    ///设置锁的属性
    pthread_mutexattr_settype(&att, PTHREAD_MUTEX_DEFAULT);
    ///初始化锁
    pthread_mutex_init(&_lock, &att);
    ///销毁属性
     pthread_mutexattr_destroy(&att);
    [self hanleMoney];
/******************************递归锁验证******************************/
    ///初始化属性
    pthread_mutexattr_t recursiveAtt ;
    pthread_mutexattr_init(&recursiveAtt);
    ///设置锁的属性
    pthread_mutexattr_settype(&recursiveAtt, PTHREAD_MUTEX_RECURSIVE);
    ///初始化锁
    pthread_mutex_init(&_recursiveLock, &recursiveAtt);
    ///销毁属性
    pthread_mutexattr_destroy(&recursiveAtt);
    [self recuresiveAction];
}
- (void)hanleMoney {
    __weak typeof(self) weakSelf = self;
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i ++) {
            [weakSelf saveMoney];
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i ++) {
            [weakSelf drawMoney];
        }
    });
}
- (void)saveMoney{
    pthread_mutex_lock(&_lock);
    [super saveMoney];
    pthread_mutex_unlock(&_lock);
}
- (void)drawMoney {
    pthread_mutex_lock(&_lock);
    [super drawMoney ];
    pthread_mutex_unlock(&_lock);
}
/*************************************递归锁********************************************/
- (void)recuresiveAction {
    static int count = 10;
    pthread_mutex_lock(&_recursiveLock);
    NSLog(@"count: %d", count);
    if (count > 0) {
        count --;
        [self recuresiveAction];
    }
    pthread_mutex_unlock(&_recursiveLock);
}
/*************************************递归锁********************************************/
- (void)dealloc {
    pthread_mutex_destroy(&_lock);
    pthread_mutex_destroy(&_recursiveLock);
}
@end
  1. 关于条件锁的验证代码
    首先设定以下使用场景, 两条线程 AB, A线程中执行删除数组元素, B线程中执行添加数组元素;由于不知道哪个线程会先执行, 所以需要加锁实现, 只有在添加后才能执行删除操作;为互斥锁添加条件可以实现;
    通过此方法可以实现线程依赖;
#import "ViewController4.h"
#import <pthread.h>

@interface ViewController4 ()
@property (nonatomic, strong) NSMutableArray *dataArr;
@property (nonatomic, assign) pthread_mutex_t lock;
@property (nonatomic, assign) pthread_cond_t condition;
@end
@implementation ViewController4
- (void)viewDidLoad {
    [super viewDidLoad];
    /**
     首先设定以下使用场景, 两条线程 A和B, A线程中执行删除数组元素, B 线程中执行添加数组元素;
     由于不知道哪个线程会先执行, 所以需要加锁实现, 只有在添加后才能执行删除操作;为互斥锁添加条件可以实现;
     */
    ///初始化锁
    pthread_mutexattr_t att;
    pthread_mutexattr_init(&att);
    pthread_mutexattr_settype(&att, PTHREAD_MUTEX_DEFAULT);
    pthread_mutex_init(&_lock, &att);
    pthread_mutexattr_destroy(&att);
    ///初始化条件
    pthread_cond_init(&_condition, NULL);
    NSThread *deThread = [[NSThread alloc] initWithTarget:self selector:@selector(deleteObj) object:nil];
    [deThread start];
    ///sleep一秒. 确保删除元素的线程先获得锁;
    sleep(1);
    NSThread *adThread = [[NSThread alloc] initWithTarget:self selector:@selector(addObj) object:nil];
    [adThread start];
    
}
- (void)deleteObj {
    pthread_mutex_lock(&_lock);
    NSLog(@"delete begin");
    ///添加判断, 如果没有数据则添加条件
    if (self.dataArr.count < 1) {
        /**
         添加条件, 如果数组为空, 则添加等待线程休眠, 将锁让出;  接受到信号时会再次加锁, 然后继续向下执行;
         */
        pthread_cond_wait(&_condition, &_lock);
    }
    
    [self.dataArr removeLastObject];
    NSLog(@"数组执行删除元素操作");
    pthread_mutex_unlock(&_lock);
}
- (void)addObj {
    pthread_mutex_lock(&_lock);
    NSLog(@"add begin");
    [self.dataArr addObject:@"HTI"];
    ///发送新号, 说明已经添加元素了;
    pthread_cond_signal(&_condition);
    NSLog(@"数组执行添加元素操作");
    pthread_mutex_unlock(&_lock);
}
- (void)dealloc {
    pthread_mutex_destroy(&_lock);
    pthread_cond_destroy(&_condition);
}
@end

补充. 为什么会出现死锁?

当两个线程中其中一个线程获得锁并加锁后, 如果使用完没有解锁, 则另一个线程就会一直处于等待解锁状态, 就会形成死锁;


文中测试代码
参考文章
不再安全的OSSpinLock
objc4源码下载地址
libplatform源码下载地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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