什么是锁?
在计算机科学中,锁是一种同步机制.用于在存在多线程的环境中实施对资源访问的限制.可以理解它的作用是排除并发的一种策略!
在iOS中,锁大致可分为递归锁
,条件锁
,分布式锁
,一般锁
(根据NSLock类中的分类进行划分).
常用的有一下几种:
- @synchronized 互斥锁
- NSLock 互斥锁
- NSCondition 条件锁
- NSConditionLock 条件锁
- NSRecursiveLock 递归锁
- pthread_mutex 互斥锁(C语言)
- dispatch_semaphore 信号量实现加锁(GCD)
- OSSpinLock 自旋锁
自旋锁和互斥锁对比?
相同点: 都能保证同一时间只有一个线程访问共享资源,都能保证线程安全.
不同点:
互斥锁:如果共享资源已经有其他线程加锁了,线程会进入休眠状态等待加锁.一旦被访问的资源被解锁了,则等待资源的线程会立即执行.
自旋锁:如果共享资源数据有其他线程加锁了,线程会以死循环的方式等待加锁,一旦被访问的资源被解锁,则等待资源的线程会立即执行.
自旋锁的效率要高于互斥锁
使用自旋锁时要注意什么?
由于自旋时不释放CPU,因此持有自旋锁的线程应该尽快释放自旋锁,否则等待该自旋锁的线程会一直在那里自旋,这样会浪费CPU时间.
持有自旋锁的线程在sleep之前应该释放自旋锁以便于其他可以获取该自旋锁.内核编程中,如果持有自旋锁的代码sleep了就有可能导致整个系统挂起.
使用任何锁都是需要消耗系统资源(内存资源和CPU时间),这种资源消耗可以分为两类:
- 建立锁所需要的资源
- 当线程被阻塞时所需要的资源
两种锁的加锁原理:
互斥锁: 线程会从sleep(加锁)->running(解锁),过程中有上下文的切换,CPU的抢占,信号的发送等开销.
自旋锁: 线程一直在running(加锁->解锁),死循环检测锁的标志位,机制不复杂.
在iOS中实现自旋锁(以OSSpinLock为例)
导入头文件
#import <libkern/OSAtomic.h>
代码实现:
__block OSSpinLock oslock = OS_SPINLOCK_INIT;
//创建线程1 执行加锁操作,并sleep4秒之后执行解锁操作
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程1 准备加锁");
OSSpinLockLock(&oslock);
NSLog(@"线程1 加锁成功");
sleep(4);
OSSpinLockUnlock(&oslock);
NSLog(@"线程1 解锁成功");
});
//创建线程2 执行加锁和解锁操作
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程2 准备加锁");
OSSpinLockLock(&oslock);
NSLog(@"线程2 加锁成功");
OSSpinLockUnlock(&oslock);
NSLog(@"线程2 解锁成功");
NSLog(@"---------------------------");
});
可能出现的结果如下:
第一种情况:
2021-01-05 14:19:31.877927+0800 test1[24762:377439] 线程2 准备加锁
2021-01-05 14:19:31.877936+0800 test1[24762:377442] 线程1 准备加锁
2021-01-05 14:19:31.878130+0800 test1[24762:377439] 线程2 加锁成功
2021-01-05 14:19:31.878258+0800 test1[24762:377439] 线程2 解锁成功
2021-01-05 14:19:31.878379+0800 test1[24762:377439] ---------------------------
2021-01-05 14:19:31.879170+0800 test1[24762:377442] 线程1 加锁成功
2021-01-05 14:19:35.884416+0800 test1[24762:377442] 线程1 解锁成功
第二种情况:
2021-01-05 14:23:03.797474+0800 test1[25001:381819] 线程1 准备加锁
2021-01-05 14:23:03.797780+0800 test1[25001:381819] 线程1 加锁成功
2021-01-05 14:23:03.799260+0800 test1[25001:381817] 线程2 准备加锁
2021-01-05 14:23:07.802987+0800 test1[25001:381819] 线程1 解锁成功
2021-01-05 14:23:07.849122+0800 test1[25001:381817] 线程2 加锁成功
2021-01-05 14:23:07.849530+0800 test1[25001:381817] 线程2 解锁成功
2021-01-05 14:23:07.849778+0800 test1[25001:381817] ---------------------------
互斥锁的实现(以pthread_mutex为例)
导入头文件
#import <pthread.h>
代码实现:
static pthread_mutex_t plock;
pthread_mutex_init(&plock, NULL);
//创建线程1 执行加锁操作,并sleep4秒之后执行解锁操作
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程1 准备加锁");
pthread_mutex_lock(&plock);
NSLog(@"线程1 加锁成功");
sleep(4);
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(@"线程2 解锁成功");
NSLog(@"------------------");
});
可能出现的结果如下:
第一种情况:
2021-01-05 14:44:15.233326+0800 test1[26503:409089] 线程2 准备加锁
2021-01-05 14:44:15.233326+0800 test1[26503:409092] 线程1 准备加锁
2021-01-05 14:44:15.233550+0800 test1[26503:409092] 线程1 加锁成功
2021-01-05 14:44:19.239138+0800 test1[26503:409092] 线程1 解锁成功
2021-01-05 14:44:19.239143+0800 test1[26503:409089] 线程2 加锁成功
2021-01-05 14:44:19.239331+0800 test1[26503:409089] 线程2 解锁成功
2021-01-05 14:44:19.239463+0800 test1[26503:409089] ------------------
第二种情况:
2021-01-05 14:46:16.258085+0800 test1[26669:412086] 线程2 准备加锁
2021-01-05 14:46:16.258094+0800 test1[26669:412088] 线程1 准备加锁
2021-01-05 14:46:16.258308+0800 test1[26669:412086] 线程2 加锁成功
2021-01-05 14:46:16.258475+0800 test1[26669:412086] 线程2 解锁成功
2021-01-05 14:46:16.258480+0800 test1[26669:412088] 线程1 加锁成功
2021-01-05 14:46:16.258611+0800 test1[26669:412086] ------------------
2021-01-05 14:46:20.262960+0800 test1[26669:412088] 线程1 解锁成功