在IOS上进行多线程开发,为了保证线程安全,防止资源竞争,须要给进程进行加锁,一般用到的进程锁分为7种。ios
- 信号量
- 互斥锁
- 自旋锁
- 递归锁
- 条件锁
- 读写锁
- 分布式锁
锁:是保证线程安全常见的同步工具,防止Data race(数据竞争)的发生。数组
Data race(数据竞争):安全
- 两个或者更多线程在一个程序中,并发的访问同一数据
- 至少一个访问是写入操做
- 些线程都不使用任何互斥锁来控制这些访问
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); // 定义锁的属性
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr) // 建立锁
pthread_mutex_lock(&mutex); // 申请锁
pthread_mutex_unlock(&mutex); // 释放锁
复制代码
其中,锁的属性包含一下四种:bash
PTHREAD_MUTEX_NORMAL:默认值普通锁,当一个线程加锁之后,其余线程进入按照优先顺序进入等待队列,而且解锁的时候按照先入先出的方式得到锁。
PTHREAD_MUTEX_ERRORCHECK:检错锁,当同一个线程得到同一个锁的时候,则返回EDEADLK,不然与普通锁处理同样。
PTHREAD_MUTEX_RECURSIVE:递归锁。这里有别于上面的检错锁,同一个线程能够递归得到锁,可是加锁和解锁必需要一一对应。
PTHREAD_MUTEX_DEFAULT:适应锁,等待解锁以后从新竞争,没有等待队列。
复制代码
dispatch_semaphore
是GCD用来同步的一种方式,dispatch_semephore_create
方法用户建立一个dispatch_semephore_t
类型的信号量,初始的参数必须大于0,该参数用来表示该信号量有多少个信号,简单的说也就是同事容许多少个线程访问。dispatch_semaphore_wait
方法是等待一个信号量,该方法会判断signal
的信号值是否大于0,若是大于0则不会阻塞线程,消耗点一个信号值,执行后续任务。若是信号值等于0那么就和NSCondition
同样,阻塞当前线程进入等待状态,若是等待时间未超过timeout而且dispatch_semaphore_signal
释放了了一个信号值,那么就会消耗掉一个信号值而且向下执行。若是期间一直不能得到信号量而且超过超时时间,那么就会自动执行后续语句。多线程
dispatch_semaphore_create(long value)
;//创造信号量dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
;//等待信号dispatch_semaphore_signal(dispatch_semaphore_t dsema)
;//发送信号
实例代码:并发
- (void)semaphoreSync {
NSLog(@"semaphore---begin");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//建立初始信号量 为 0 ,阻塞全部线程
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block int number = 0;
dispatch_async(queue, ^{
// 追加任务A
[NSThread sleepForTimeInterval:2]; // 模拟耗时操做
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
number = 100;
// 执行完线程,信号量加 1,信号总量从 0 变为 1
dispatch_semaphore_signal(semaphore);
});
//原任务B
////若计数为0则一直等待,直到接到总信号量变为 >0 ,继续执行后续代码
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore---end,number = %d",number);
}
复制代码
互斥锁的实现原理与信号量很是类似,不是使用忙等,而是阻塞线程并睡眠,须要进行上下文切换。
当一个线程得到这个锁以后,其余想要得到此锁的线程将会被阻塞,直到该锁被释放。
当临界区加上互斥锁之后,其余的调用方不能得到锁,只有当互斥锁的持有方释放锁以后其余调用方才能得到锁。
调用方在得到锁的时候发现互斥锁已经被其余方持有,那么该调用方只能进入睡眠状态,这样不会占用CPU资源。可是会有时间的消耗,系统的运行时基于CPU时间调度的,每次线程可能有100ms的运行时间,频繁的CPU切换也会消耗必定的时间。app
NSLock遵循NSLocking协议,同时也是互斥锁,提供了lock
和unlock
方法来进行加锁和解锁。 NSLock内部是封装了pthread_mutext
,类型是PTHREAD_MUTEXT_ERRORCHECK
,它会损失必定的性能换来错误提示。async
- (void)lock;
- (void)unlock;
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
复制代码
tryLock
和lock
方法都会请求加锁,惟一不一样的是trylock
在没有得到锁的时候能够继续作一些任务和处理。lockBeforeDate:
方法也比较简单,就是在limit时间点以前得到锁,没有拿到锁就返回NO。分布式
这实际上是一个 OC 层面的锁,防止不一样的线程同时执行同一段代码,相比于使用 NSLock 建立锁对象、加锁和解锁来讲,
@synchronized
用着更方便,可读性更高。
大致上,想要明白@synchronized
,须要知道在@synchronized
中objc_sync_enter
和objc_sync_exit
的成对调用,并且每一个传入的对象,都会为其分配一个递归锁并存储在哈希表中。在objc_sync_enter
中加锁,在objc_sync_exit
中解锁。
具体能够参考这篇文章: 关于 @synchronized,这儿比你想知道的还要多函数
@synchronized(self) {
//数据操做
}
复制代码
自旋锁的目的是为了确保临界区只有一个线程能够访问。
当一个线程得到锁以后,其余线程将会一直循环在哪里查看是否该锁被释放,是用于多线程同步的一种锁,线程反复检查锁变量是否可用。因为线程在这一过程当中保持执行,所以是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁。自旋锁避免了进程上下文的调度开销,所以对于线程只会阻塞很短期的场合是有效的。
因为调用方会一直循环看该自旋锁的的保持者是否已经释放了资源,因此总的效率来讲比互斥锁高。可是自旋锁只用于短期的资源访问,若是不能短期内得到锁,就会一直占用着CPU,形成效率低下。
自旋锁的一种,因为在某些场景下不安全已被弃用。 需导入头文件
#import <libkern/OSAtomic.h>
OSSpinLock lock = OS_SPINLOCK_INIT;
OSSpinLockLock(&lock);
OSSpinLockUnlock(&lock);
OSSpinLockTry(&lock);
复制代码
自旋锁存在优先级反转问题,OSSpinLock
是自旋锁,也正是因为它是自旋锁,因此容易发生优先级反转的问题。在ibireme的文章中已经写到,当一个低优先级线程得到锁的时候,若是此时一个高优先级的系统到来,那么会进入忙等状态,不会进入睡眠,此时会一直占用着系统CPU时间,致使低优先级的没法拿到CPU时间片,从而没法完成任务也没法释放锁。除非能保证访问锁的线程所有处于同一优先级,不然系统全部的自旋锁都会出现优先级反转的问题。如今苹果的OSSpinLock
已经被替换成
os_unfair_lock
typedef int32_t OSSpinLock OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock);
复制代码
os_unfair_lock
是苹果官方推荐的替换OSSpinLock
的方案,用于解决OSSpinLock
优先级反转问题,可是它在iOS10.0以上的系统才能够调用。
os_unfair_lock
非自旋锁,是一个互斥锁,引用SoC兄的文章os_unfair_lock的类型和自旋锁与互斥锁的比较,包括在在apple的官方文档里面也是写明的了,“ This function doesn't spin on contention, but instead waits in the kernel to be awoken by an unlock”,文中只是指出os_unfair_lock
是苹果推出替代OSSpinLock
的一种相对高效的锁,并非说其是自旋锁。
导入头文件#import< os/lock.h >
os_unfair_lock_t unfairLock;
unfairLock = &(OS_UNFAIR_LOCK_INIT);
os_unfair_lock_lock(unfairLock);
os_unfair_lock_unlock(unfairLock);
os_unfair_lock_trylock(unfairLock);
复制代码
须要使用递归锁的状况:
NSLock *lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveLock)(int);
RecursiveLock = ^(int value) {
[lock lock];
if (value > 0) {
NSLog(@"value = %d", value);
sleep(2);
RecursiveLock(value - 1);
}
[lock unlock];
};
RecursiveLock(5);
});
复制代码
这段代码是一个典型的死锁状况。在咱们的线程中,RecursiveMethod是递归调用的。全部每次进入这个block时,都会去加一次锁,而从第二次开始,因为锁已经被使用了且没有解锁,全部它须要等待锁被解除,这样就致使了死锁,线程被阻塞住了。致使crach
*** -[NSLock lock]: deadlock ( '(null)') *** Break on _NSLockError() to debug.
复制代码
递归锁也是经过
pthread_mutex_lock
函数来实现,在函数内部会判断锁的类型,若是显示是递归锁,就容许递归调用,仅仅将一个计数器加一,锁的释放过程也是同理。
一个锁能够被同一线程屡次请求,而不会引发死锁。这主要是用在循环或递归操做中。NSRecursiveLock
与NSLock
的区别在于内部封装的pthread_mutex_t
对象的类型不一样,前者的类型为PTHREAD_MUTEX_RECURSIVE
。
主要操做:
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
// 建立递归锁[lock lockBeforeDate:date];
// 在给定的时间以前去尝试请求一个锁[lock tryLock];
// 尝试去请求一个锁,并会当即返回一个布尔值,表示尝试是否成功
另外,NSRecursiveLock还声明了一个name属性,以下:
@property(copy) NSString *name
咱们可使用这个字符串来标识一个锁。Cocoa也会使用这个name做为错误描述信息的一部分。
封装了一个互斥锁和信号量,它把前者的
lock
以及后者的wait
/signal
统一到NSCondition
对象中,是基于条件变量pthread_cond_t
来实现的,和信号量类似,若是当前线程不知足条件,那么就会进入睡眠状态,等待其余线程释放锁或者释放信号以后,就会唤醒线程。
NSCondition 的对象实际上做为一个锁和一个线程检查器:锁主要为了当检测条件时保护数据源,执行条件引起的任务;线程检查器主要是根据条件决定是否继续运行线程,即线程是否被阻塞。
NSCondition
一样实现了NSLocking
协议,因此它和NSLock
同样,也有NSLocking协议的lock
和unlock
方法,能够当作NSLock
来使用解决线程同步问题,用法彻底同样。NSCondition
提供了wait
和signal
,和条件信号量相似。好比咱们要监听array数组的个数,当array的个数大于0的时候就执行清空操做。思路是这样的,当array个数大于0时执行清空操做,不然,wait
等待执行清空操做。当array个数增长的时候发生signal
信号,让等待的线程唤醒继续执行。NSCondition *lock = [[NSCondition alloc] init];
NSMutableArray *array = [[NSMutableArray alloc] init];
//消费者
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
while (!array.count) {
[lock wait];
}
[array removeAllObjects];
[lock unlock];
});
//生产者
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);//以保证让线程2的代码后执行
[lock lock];
[array addObject:@1];
[lock signal];
[lock unlock];
});
复制代码
NSCondition
能够给每一个线程分别加锁,加锁后不影响其余线程进入临界区。可是正是由于这种分别加锁的方式,NSCondition使用wait并使用加锁后并不能真正的解决资源的竞争。例如:
不能让m<0。假设当前m=0,线程A要判断到m>0为假,执行等待;线程B执行了m=1操做,并唤醒线程A执行m-1操做的同时线程C判断到m>0,由于他们在不一样的线程锁里面,一样判断为真也执行了m-1,这个时候线程A和线程C都会执行m-1,可是m=1,结果就会形成m=-1.
NSConditionLock也能够像NSCondition同样作多线程之间的任务等待调用,并且是线程安全的。
NSConditionLock一样实现了NSLocking协议,性能比较低。
NSConditonLock
内部持有了一个NSCondition
对象和_condition_value
属性,当调用
- (instancetype)initWithCondition:(NSInteger)condition
初始化的时候会传入一个condition
参数,该参数会赋值_condition_value
属性
lock
不分条件,若是锁没被申请,直接执行代码lockBeforeDate:
在指定时间前尝试加锁,返回bool
lockWhenCondition:
知足特定条件Condition
,加锁执行相应代码lockWhenCondition: beforeDate:
和上条相同,增长时间戳tryLock
尝试着加锁,返回bool
tryLockWhenCondition:
,知足特定条件Condition
,尝试着加锁,返回bool
unlock
不会清空条件,以后知足条件的锁还会执行unlockWithCondition:
设置解锁条件(同一时刻只有一个条件,若是已经设置条件,至关于修改条件)
- (void)executeNSConditionLock {
NSConditionLock* lock = [[NSConditionLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (NSUInteger i=0; i<3; i++) {
sleep(2);
if (i == 2) {
[lock lock];
[lock unlockWithCondition:i];
}
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
[self threadMethodOfNSCoditionLock:lock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
[self threadMethodOfNSCoditionLock:lock];
});
}
-(void)threadMethodOfNSCoditionLock:(NSConditionLock*)lock{
[lock lockWhenCondition:2];
[lock unlock];
}
复制代码
读写锁,在对文件进行操做的时候,写操做是排他的,一旦有多个线程对同一个文件进行写操做,后果不可估量,但读是能够的,多个线程读取时没有问题的。
读写锁能够有三种状态:
- 读模式下加锁状态,
- 写模式下加锁状态,
- 不加锁状态。
一次只有一个线程能够占有写模式的读写锁,可是多个线程可用同时占有读模式的读写锁。读写锁也叫作共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的,当它以写模式锁住时,它是以独占模式锁住的。
所以:
- 当读写锁被一个线程以读模式占用的时候,写操做的其余线程会被阻塞,读操做的其余线程还能够继续进行
- 当读写锁被一个线程以写模式占用的时候,写操做的其余线程会被阻塞,读操做的其余线程也被阻塞。
注意:
- 若是本身已经获取了读锁,再去加写锁,会出现死锁的
// 初始化
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
//获取一个读出锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwptr);
//获取一个写入锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwptr);
int pthread_rwlock_unlock(pthread_rwlock_t *rwptr); //释放一个写入锁或者读出锁
//读写锁属性:
int pthread_rwlock_init(pthread_rwlock_t *rwptr, const pthread_rwlockattr_t *attr)
int pthread_rwlock_destroy(pthread_rwlock_t *rwptr);
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int *valptr);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int valptr);
复制代码
实例:
// 初始化
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER
// 读模式
pthread_rwlock_wrlock(&lock);
// 写模式
pthread_rwlock_rdlock(&lock);
// 读模式或者写模式的解锁
pthread_rwlock_unlock(&lock);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self readBookWithTag:1];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self readBookWithTag:2];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self writeBook:3];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self writeBook:4];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self readBookWithTag:5];
});
- (void)readBookWithTag:(NSInteger )tag {
pthread_rwlock_rdlock(&rwLock);
self.path = [[NSBundle mainBundle] pathForResource:@"1" ofType:@".doc"];
self.contentString = [NSString stringWithContentsOfFile:self.path encoding:NSUTF8StringEncoding error:nil];
pthread_rwlock_unlock(&rwLock);
}
- (void)writeBook:(NSInteger)tag {
pthread_rwlock_wrlock(&rwLock);
[self.contentString writeToFile:self.path atomically:YES encoding:NSUTF8StringEncoding error:nil];
pthread_rwlock_unlock(&rwLock);
}
复制代码
NSDistributedLock
,分布锁,文件方式实现,能够跨进程 用tryLock
方法获取锁。 用unlock
方法释放锁。若是一个获取锁的进行在释放锁以前挂了,那么锁就一直得不到释放了,此时能够经过breakLock
强行获取锁。
注:这种锁在ios上基本用不到,不过多探究。