按照功能来区分锁:
互斥锁(mutexlock)sleep-waiting:
保证共享数据操作的完整性, 锁被占用的时候会休眠, 等待锁释放的时候会唤醒。
在访问共享资源之前进行加锁,访问完成后解锁。
线程A获取到锁,在释放锁之前,其他线程都获取不到锁。
解锁时,如果有1个以上的线程阻塞,那么所有该锁上的线程变为就绪状态,第一个就绪的加锁,其他的又进入休眠。
从而实现在任意时刻,最多只有1个线程能够访问被互斥锁保护的资源。
自旋锁(spinlock)busy-waiting:
跟互斥锁类似, 只是资源被占用的时候, 会一直循环检测锁是否被释放(CPU不能做其他的事情)
线程A获取到锁,在释放锁之前,线程B 来获取锁,此时获取不到,线程B就会不断的进入循环,一直检查锁是否已被释放,如果释放,则能获取到锁。
由于调用方会一直循环看该自旋锁的的保持者是否已经释放了资源,节省了唤醒睡眠线程的内核消耗。
但是如果不能短时间获得锁,就会一直占用着CPU,造成效率低下。
(在加锁时间短暂的情况下会大大提高效率)
其他锁都是这两种锁的延伸和扩展。
常见的锁
1、OSSpinLock
自旋锁,会造成优先级反转的问题。当一个低优先级线程获得锁的时候,如果此时一个高优先级的系统到来,那么会进入忙等状态,不会进入睡眠,此时会一直占用着系统CPU时间,导致低优先级的无法拿到CPU时间片,从而无法完成任务也无法释放锁。除非能保证访问锁的线程全部处于同一优先级,否则系统所有的自旋锁都会出现优先级反转的问题。
现在苹果的OSSpinLockiOS10+已废弃,已经被替换成os_unfair_lock。,有兴趣可以看看:不再安全的OSSpinLock。
2、os_unfair_lock
互斥锁,iOS10+才支持,为了替代OSSpinLock。
os_unfair_lock 是一种适用于需要简单和轻量级互斥的情况下的锁。
此锁必须从与锁定它的相同线程中解锁,尝试从不同线程解锁将导致断言中止进程。
必须使用 OS_UNFAIR_LOCK_INIT 进行初始化。使用时需要引入#import <os/lock.h>
// 必须在线程里初始化 且必须使用OS_UNFAIR_LOCK_INIT初始化
os_unfair_lock_t unfairLock = &(OS_UNFAIR_LOCK_INIT);
os_unfair_lock_lock(unfairLock); // 加锁
......其他操作......
os_unfair_lock_unlock(unfairLock); // 解锁
3、pthread_mutex
互斥锁,等待锁的线程会处于休眠状态。
使用时需要引入头文件#import <pthread.h>
// 定义互斥锁和互斥锁属性对象
// 若不特殊设置互斥锁属性对象可以不设置
// 初始化互斥锁时的第二个参数可以传NULL
pthread_mutex_t mutex;
pthread_mutexattr_t mutex_attr;
// 初始化互斥锁属性对象
pthread_mutexattr_init(&mutex_attr);
// 设置互斥锁属性为默认类型
pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_DEFAULT);
// 初始化互斥锁
pthread_mutex_init(&mutex, &mutex_attr);
// 加锁
pthread_mutex_lock(&mutex);
// 访问共享资源
// 这里省略了共享资源的操作
// 解锁
pthread_mutex_unlock(&mutex);
// 销毁互斥锁
pthread_mutex_destroy(&mutex);
// 销毁互斥锁属性对象
pthread_mutexattr_destroy(&mutex_attr);
#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_ERRORCHECK:错误检查锁。提供死锁检测。如果同一线程尝试再次获取锁,函数将返回错误 EBUSY。
PTHREAD_MUTEX_RECURSIVE:递归锁。允许同一线程多次获取锁而不会死锁。必须在解锁时与加锁次数匹配。
PTHREAD_MUTEX_DEFAULT:等同于 PTHREAD_MUTEX_NORMAL。
4、NSLock
互斥锁,遵循NSLocking协议。内部是封装了pthread_mutext,类型是PTHREAD_MUTEXT_ERRORCHECK。
NSLock、NSCondition、NSConditionLock、NSRecursiveLock都实现了NSLocking协议。了解更多查看官方文档
@protocol NSLocking
- (void)lock;
- (void)unlock ;
@end
-lock和-unlock必须在相同的线程调用。
5、NSCondition
互斥锁,也遵循NSLocking协议。内部是封装了pthread_mutext,和它和NSLock的区别是:NSLock在获取不到锁的时候自动使线程进入休眠,锁被释放后线程又自动被唤醒,NSCondition可以使我们更加灵活的控制线程状态,在任何需要的时候使线程进入休眠或唤醒它。
// 让当前线程处于等待状态
- (void)wait;
// 向等待在条件上的一个线程发出信号,可以继续执行
- (void)signal;
// 向等待在条件上的所有线程发出信号,从而唤醒所有线程
- (void)broadcast;
NSCondition *cLock = [NSCondition new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lock];
NSLog(@"线程1加锁成功");
[cLock wait];
NSLog(@"线程1");
[cLock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lock];
NSLog(@"线程2加锁成功");
[cLock wait];
sleep(1);
NSLog(@"线程2");
[cLock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"唤醒一个等待的线程");
[cLock signal];
});
输出:
线程1加锁成功
线程2加锁成功
唤醒一个等待的线程
线程1
6、NSConditionLock
互斥锁,有条件的互斥锁。
// 只读属性condition,保存锁当前的条件
@property (readonly) NSInteger condition;// 解锁并设置新的条件值。
-(void)unlockWithCondition:(NSInteger)condition;
// 条件值满足时加锁, 执行之后代码
-(BOOL)lockWhenCondition:(NSInteger)condition;
// 当条件值等于指定值时尝试加锁。
-(BOOL)tryLockWhenCondition:(NSInteger)condition;
NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0]; // 设置初始标志位
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if ([cLock tryLockWhenCondition:0]) {
NSLog(@"Task - 1, cLock.condition = %ld",cLock.condition);
[cLock unlockWithCondition:1];
NSLog(@"Task - 1, end - cLock.condition = %ld",cLock.condition);
} else {
NSLog(@"Task - 加锁失败");
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lockWhenCondition:3];
NSLog(@"Task - 2, cLock.condition = %ld",cLock.condition);
[cLock unlockWithCondition:2];
NSLog(@"Task - 2, end- cLock.condition = %ld",cLock.condition);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lockWhenCondition:1];
NSLog(@"Task - 3, cLock.condition = %ld",cLock.condition);
[cLock unlockWithCondition:3];
NSLog(@"Task - 3, end- cLock.condition = %ld",cLock.condition);
});
输出:
Task - 1, cLock.condition = 0
Task - 1, end - cLock.condition = 1
Task - 3, cLock.condition = 1
Task - 3, end- cLock.condition = 3
Task - 2, cLock.condition = 3
Task - 2, end- cLock.condition = 2
7、NSRecursiveLock
互斥锁中的递归锁。内部使用的pthread_mutex_t的类型是PTHREAD_MUTEXT_RECURSIVE。可以被同一线程多次请求,而不会引起死锁。这主要是用在循环或递归操作中。
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
NSLock *lo = [NSLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveMethod)(int);
RecursiveMethod = ^(int value) {
NSLog(@"加锁: %d", value);
[lock lock]; // 加锁
if (value < 3) {
sleep(2);
RecursiveMethod(++value); // 解锁之前又锁上
}
NSLog(@"解锁: %d", value);
[lock unlock]; // 解锁
};
RecursiveMethod(1);
NSLog(@"finish");
});
输出:
加锁: 1
加锁: 2
加锁: 3
解锁: 3
解锁: 3
解锁: 2
finish
8、@synchronized
@synchronized(id)的使用应该是较多的,它底层实现是个递归锁,不会产生死锁,且不需要手动去加锁解锁,使用起来比较方便。
@synchronized(self) {
//数据操作
}
9、atomic
修饰属性关键字,对被修饰的进行原子操作(不负责使用,只是setter/getter方法安全,其他不保证)点击属性关键字-atomic了解更多。
10、dispatch_semaphore_t
是GCD用来同步的一种方式。
GCD 信号量dispatch_semaphore可以用来控制最大并发数量,可以用来实现 iOS 的线程同步方案。
信号量的初始值,可以用来控制线程并发访问的最大数量;
信号量的初始值为1,代表同时只允许 1 条线程访问资源,保证线程同步。
若设置信号量初始值为3.则控制最大并发数为3。// 创建一个 Semaphore 并初始化信号的总量
dispatch_semaphore_create// 发送一个信号,让信号总量加 1
dispatch_semaphore_signal// 可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。
dispatch_semaphore_wait
点击iOS:多线程,了解更多信号量相关信息。
锁的关系及性能
锁之间的关系
性能排序(从高到低):
1、os_unfair_lock
2、OSSpinLock(已经不推荐使用)
3、dispatch_semaphore
4、pthread_mutext
5、NSLock
6、NSCondition
7、NSRecursiveLock
8、NSConditonLock
9、@synchronized
死锁
指多个进程因竞争共享资源而造成的一种僵局,若无外力作用,这些进程都将永远不能再向前推进。
死锁的条件
1、互斥:某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
2、占有且等待:一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
3、不可抢占:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来,只能在使用完时由自己释放。
4、循环等待:存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。