主要讲解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
特点:
- 不是一个安全的锁, 等待锁的线程会处于忙等(
busy-wait
)状态, 一直占用着CPU
资源; (类似一个while(1)
循环一样,不停的查询锁的状态, 注意区分Runloop
的机制, 同样是阻塞, 但是Runloop
是类似休眠的阻塞, 不会耗费CPU
资源, 自旋锁的这种忙等机制使它相比其他锁效率更高, 因为没有唤醒-休眠这些类似操作, 从而能更快去处理事情);
注意
OSSpinLock
自旋锁iOS10
之后已经废弃使用, 它可能会导致优先级反转; 例如A/B两个线程, A的优先级大于B的, 我们的本意是A的任务优先执行; 但是使用OSSpinLock
后, 如果是B先获得了优先访问了共享资源获得了锁并加锁, 而A线程再去访问共享资源时锁就会处于忙等状态, 由于优先级高则它会一直占用CPU
资源不会让出时间片;这样B一直不能获得CPU
资源去执行任务, 导致无法完成;
- 使用时需要导入头文件
#import <libkern/OSAtomic.h>
- 使用时可能会用到的方法:
3.1OS_SPINLOCK_INIT
初始化自旋锁;
3.2OSSpinLockLock()
加锁;
3.3OSSpinLockUnlock()
解锁;
3.4OSSpinLockTry()
尝试加锁, 返回一个BOOL
值; - 示例代码如下
#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
特点
- 设计宗旨在于替换
OSSpinLock
, 从iOS10
之后开始支持; 跟OSSpinLock
不同, 等待os_unfari_lock
的线程会处于休眠状态(类似Runloop
那这样), 不是忙等; - 使用时需要引入头文件
#import <os/lock.h>
- 使用时可能会用到的方法
3.1OS_UNFAIR_LOCK_INIT
初始化锁;
3.2os_unfair_lock_lock()
加锁;
3.3os_unfair_lock_unlock()
解锁;
3.4os_unfair_lock_trylock()
尝试加锁, 返回一个BOOL
值; - 测试代码
#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
特点
- 跨平台使用的
API
, 等待锁的线程会处于休眠状态;
当使用递归锁时: 允许同一个线程重复进行加锁(另一个线程访问时就会等待), 这样可以保证多线程时访问共用资源的安全性; - 使用时需要引入头文件
#import <pthread.h>
- 使用时可能会用到的方法
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()
发送广播信号激活等待该条件的所有线程;
- 关于
互斥锁
和递归锁
的测试代码:
#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
-
关于条件锁的验证代码
首先设定以下使用场景, 两条线程A
和B
,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
补充. 为什么会出现死锁?
当两个线程中其中一个线程获得锁并加锁后, 如果使用完没有解锁, 则另一个线程就会一直处于等待解锁状态, 就会形成死锁;