[1].OSSpinLock 自旋锁
//导入头文件
#import<libkern/OSAtomic.h>
/*
//dispatch_get_global_queue 系统提供的一个并发队列
同步 异步
串行队列 当前线程,一个一个执行 其他线程,一个一个执行
并行队列 当前线程,一个一个执行 开很多线程,一起执行
*/
//线程1
__block OSSpinLock osLock = OS_SPINLOCK_INIT;dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程1 准备上锁");
OSSpinLockLock(&oslock);
sleep(4);
NSLog(@"线程1");
OSSpinLockUnlock(&oslock);
NSLog(@"线程1 解锁成功");
NSLog(@"-------------------------------");
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程2 准备上锁");
OSSpinLockLock(&oslock);
NSLog(@"线程2");
OSSpinLockUnlock(&oslock);
NSLog(@"线程2 解锁成功");
NSLog(@"################################");
});
[1]自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。由于自旋锁使用者一般保持锁 时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。
[2]图中可以发现:当我们锁住线程1时,在同时锁住线程2的情况下,线程2会一直等待(自旋锁不会让等待的进入睡眠状态,直到线程1的任务执行完且解锁完毕,线程2会立即执行;正常情况下,lock 和unlock最好成对出现。
[3]但是因为自旋锁存在优先级反转问题,简单说如果一个低优先级的线程获得锁并访问共享资源,这时一个高优先级的线程也尝试获得这个锁,它会处于 spin lock 的忙等状态从而占用大量 CPU。此时低优先级线程无法与高优先级线程争夺 CPU 时间,从而导致任务迟迟完不成、无法释放 lock。所以苹果弄了os_unfair_lock来代替自旋锁但是 只有IOS10 中才有~~~
[2]. dispatch_semaphore 信号量
//传入值必须 >=0, 若传入为0则阻塞线程并等待timeout,时间到后会执行其后的语句
dispatch_semaphore_t signal = dispatch_semaphore_create(1);
//设置timeOut NSEC_PER_SEC表示在当前时间延时3秒
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);
/*
//常用的三个函数
dispatch_semaphore_create(1): 传入值必须 >=0, 若传入为 0 则阻塞线程并等待timeout,时间到后会执行其后的语句
dispatch_semaphore_wait(signal, overTime):可以理解为 lock,会使得 signal 值 -1
dispatch_semaphore_signal(signal):可以理解为 unlock,会使得 signal 值 +1
*/
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程1 等待ing");
dispatch_semaphore_wait(signal, overTime); //signal 值 -1
NSLog(@"线程1");
dispatch_semaphore_signal(signal); //signal 值 +1
NSLog(@"线程1 发送信号");
NSLog(@"--------------------------------------------");
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程2 等待ing");
dispatch_semaphore_wait(signal, overTime);
NSLog(@"线程2");
dispatch_semaphore_signal(signal);
NSLog(@"线程2 发送信号");
});
关于信号量,我们可以用停车来比喻:
停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。信号量的值(signal)就相当于剩余车位的数目,dispatch_semaphore_wait
函数就相当于来了一辆车,dispatch_semaphore_signal
就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了(dispatch_semaphore_create(long value)),调用一次 dispatch_semaphore_signal,剩余的车位就增加一个;调用一次dispatch_semaphore_wait 剩余车位就减少一个;当剩余车位为 0 时,再来车(即调用 dispatch_semaphore_wait)就只能等待。有可能同时有几辆车等待一个停车位。有些车主没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,所以就一直等下去。
[3]. pthread_mutex 互斥锁
/*
pthread_mutex_init()函数是以动态方式创建互斥锁的,参数attr指定了新建互斥锁的属性。
如果参数attr为NULL,则使用默认的互斥锁属性,默认属性为快速互斥锁 。以下是互斥锁的属性
* PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
* PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
* PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
* PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
pthread_mutex_init(&lock,NULL);初始化锁
pthread_mutex_lock(&lock);上锁
//do your stuff
pthread_mutex_unlock(&lock);解锁
pthread_mutex_destroy(&lock);销毁锁
*/
static pthread_mutex_t pLock;
pthread_mutex_init(&pLock, NULL);
//1.线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程1 准备上锁");
pthread_mutex_lock(&pLock);
sleep(3);
NSLog(@"线程1");
pthread_mutex_unlock(&pLock);
NSLog(@"------------------");
});
//1.线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程2 准备上锁");
pthread_mutex_lock(&pLock);
NSLog(@"线程2");
pthread_mutex_unlock(&pLock);
NSLog(@"################## $");
});
使用互斥锁时要注意死锁,死锁主要发生在有多个依赖锁存在时, 会在一个线程试图以与另一个线程相反顺序锁住互斥量时发生. 如何避免死锁是使用互斥量应该格外注意的东西。
总体来讲, 有几个不成文的基本原则:
对共享资源操作前一定要获得锁。
完成操作以后一定要释放锁。
尽量短时间地占用锁。
如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC。
线程错误返回时应该释放它所获得的锁。
[4].NSLock 普通锁
//NSLock 普通锁
/*
lock、unlock:不多做解释,和上面一样
trylock:能加锁返回 YES 并执行加锁操作,相当于 lock,反之返回 NO
lockBeforeDate:这个方法表示会在传入的时间内尝试加锁,若能加锁则执行加锁操作并返回 YES,反之返回 NO
*/
NSLock *lock = [NSLock new];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程1 尝试加锁ing...");
[lock lock];
sleep(5);//睡眠5秒
NSLog(@"线程1");
[lock unlock];
NSLog(@"线程1解锁成功");
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程2 尝试加锁ing...");
BOOL x = [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:4]];
if (x) {
NSLog(@"线程2");
[lock unlock];
}else{
NSLog(@"失败");
}
});
[5]. NSCondition
//NSCondition
/*
wait:进入等待状态
waitUntilDate::让一个线程等待一定的时间
signal:唤醒一个等待的线程
broadcast:唤醒所有等待的线程
*/
NSCondition *cLock = [NSCondition new];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lock];
NSLog(@"线程1加锁成功");
[cLock wait];
NSLog(@"线程1");
[cLock unlock];
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lock];
NSLog(@"线程2加锁成功");
[cLock wait];
NSLog(@"线程2");
[cLock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"唤醒一个等待的线程");
[cLock signal];
});
[6].@synchronized 条件锁
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized (self) {
sleep(2);
NSLog(@"线程1");
}
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized (self) {
NSLog(@"线程2");
}
});
[7].NSConditionLock 条件锁
//NSConditionLock
/*
相比于 NSLock 多了个 condition 参数,我们可以理解为一个条件标示。
*/
//线程1
NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if([cLock tryLockWhenCondition:0]){
NSLog(@"线程1");
[cLock unlockWithCondition:1];
}else{
NSLog(@"失败");
}
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lockWhenCondition:3];
NSLog(@"线程2");
[cLock unlockWithCondition:2];
});
//线程3
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[cLock lockWhenCondition:1];
NSLog(@"线程3");
[cLock unlockWithCondition:3];
});
我们在初始化 NSConditionLock 对象时,给了他的标示为 0
执行 tryLockWhenCondition:时,我们传入的条件标示也是 0,所 以线程1 加锁成功
执行 unlockWithCondition:时,这时候会把condition由 0 修改为 1
因为condition 修改为了 1, 会先走到 线程3,然后 线程3 又将 condition 修改为 3
最后 走了 线程2 的流程
从上面的结果我们可以发现,NSConditionLock 还可以实现任务之间的依赖。