锁的种类
在OC中锁分为互斥锁
和自旋锁
两种。
互斥锁
-
用于保护临界区,确保
同一
时间,只有一条线程
能够执行 -
如果代码中只有一个地方需要加锁,大多都使用
self
,这样可以避免单独
再创建一个锁对象
-
加了互斥锁的代码,当新线程访问时,如果发现其他线程正在执行锁定的代码,新线程就会进入
休眠
针对互斥锁,还需要注意以下几点:
-
互斥锁的锁定范围,应该
尽量小
,锁定范围越大
,效率越差
-
能够加锁的任意
NSObject
对象 -
锁对象一定要保证
所有
的线程都能够访问
自旋锁
-
自旋锁
与互斥锁
类似,但它不
是通过休眠
使线程阻塞,而是在获取锁之前一直处于忙等(即原地打转,称为自旋)
阻塞状态 -
使用场景:锁
持有
的时间短
,且线程不希望在重新调度上花太多成本时,就需要使用自旋锁,属性修饰符atomic
,本身就有一把自旋锁
-
加入了自旋锁,当新线程访问代码时,如果发现有其他线程正在锁定代码,新线程会用
死循环
的方法,一直等待锁定的代码执行完成,即不停的尝试执行代码,比较消耗性能
互斥锁与自旋锁的异同
-
同:
- 在同一时间,保证了只有一条线程执行任务,即保证了相应同步的功能
-
不同:
-
互斥锁
:发现其他线程执行,当前线程休眠
(即就绪状态),进入等待执行,即挂起。一直等其他线程打开之后,然后唤醒执行 -
自旋锁
:发现其他线程执行,当前线程一直询问
(即一直访问),处于忙等
状态,耗费
的性能比较高
-
-
使用场景:
-
根据任务
复杂度区分
,使用不同的锁,但判断不全时,更多是使用互斥锁去处理 当前的任务状态比较短小精悍
时,用自旋锁
-
反之的,用
互斥锁
-
八大锁
- 锁的性能数据
-
属于互斥锁:
- @synchronized
- pthread_mutex
- NSLock
- NSCondition
-
属于条件锁
- NSCondition
- NSConditionLock
-
属于自旋锁
- OSSpinLock
- atomic
图中锁的性能从高到底依次是:OSSpinLock(自旋锁) -> dispatch_semaphone(信号量) -> pthread_mutex(互斥锁) -> NSLock(互斥锁) -> NSCondition(条件锁) -> pthread_mutex(recursive 互斥递归锁) -> NSRecursiveLock(递归锁) -> NSConditionLock(条件锁) -> synchronized(互斥锁)
1. @synchronized
@synchronized是递归互斥锁
1.1 @synchronized的使用
- 新建三个异步线程分别调用
saleTicket
saleTicket
中对属性ticketCount
进行写操作。
- (void)lg_testSaleTicket{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 3; i++) {
[self saleTicket];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10; i++) {
[self saleTicket];
}
});
}
- (void)saleTicket{
// 加锁 - 线程安全
@synchronized (self) {
if (self.ticketCount > 0) {
self.ticketCount--;
sleep(0.1);
NSLog(@"当前余票还剩:%ld张",self.ticketCount);
}else{
NSLog(@"当前车票已售罄");
}
}
}
- 打印输出
1.2 源码分析
1. 如何查找源码文件
- 在main.m文件中添加一个
@synchronized
,clang
查看一下源码
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
@synchronized (appDelegateClassName) {
}
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
- 就会发现编译成了
objc_sync_enter
和_sync_exit
- 在项目中
通过汇编
debug => debug workflow => always show … 跟流程,并在objc_sync_enter
打一个断点
- 摁住control + step into,进入底层汇编,就会发现底层的实现是在
libobjc.A.dylib
中
- 在官网下载objc4-718查看源码
objc_sync_enter分析
-
查看
objc_sync_enter
-
判断obj是否为空
- part1: 不为空执行
id2data(obj, ACQUIRE);
- part2: 为空执行
objc_sync_nil
- part1: 不为空执行
-
// Begin synchronizing on 'obj'.
// Allocates recursive mutex associated with 'obj' if needed.
// Returns OBJC_SYNC_SUCCESS once lock is acquired.
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, ACQUIRE);
ASSERT(data);
data->mutex.lock();
} else {
// @synchronized(nil) does nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil();
}
return result;
}
part1: 不为空执行id2data(obj, ACQUIRE);
-
查看
SyncData
结构SyncData* nextData
=> 下一个节点object
=> 被锁对象threadCount
=> 几个线程被锁住recursive_mutex_t mutex
=> 封装的互斥锁
recursive_mutex_t
是recursive_mutex_tt
类型的结构体
typedef struct alignas(CacheLineSize) SyncData {
struct SyncData* nextData;
DisguisedPtr<objc_object> object;
int32_t threadCount; // number of THREADS using this block
recursive_mutex_t mutex;
} SyncData;
👇
-
查看
id2data
,代码可分为四
部分第一部分
在TLS
线程局部存储缓存(栈
)中查找是否存在SyncData
第二部分
在全局线程缓存列表
中查找,执行第三部分
然后再执行第四部分done
第三部分
第一次进来,给SyncData
赋值,将lockCount和threadCount都设置为1,执行done第四部分
done部分
:并将对象存入TLS
和全局线程缓存列表
中。
static SyncData* id2data(id object, enum usage why)
{
spinlock_t *lockp = &LOCK_FOR_OBJ(object);
SyncData **listp = &LIST_FOR_OBJ(object);
SyncData* result = NULL;
#if SUPPORT_DIRECT_THREAD_KEYS
// Check per-thread single-entry fast cache for matching object
//第一部分
//part1: 不是第一次进来,但是在同一个线程
//在TLS线程局部存储缓存(栈)中查找是否存在SyncData
bool fastCacheOccupied = NO;
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
if (data) {
fastCacheOccupied = YES;
if (data->object == object) {
// Found a match in fast cache.
uintptr_t lockCount;
result = data;
//获取lockCount
lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
//如果threadCount 和 lockCount都小于等于0,抛出异常
if (result->threadCount <= 0 || lockCount <= 0) {
_objc_fatal("id2data fastcache is buggy");
}
//why => 外部传入的ACQUIRE或者RELEASE
switch(why) {
//ACQUIRE lockCount + 1
case ACQUIRE: {
lockCount++;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
break;
}
//RELEASE lockCount - 1
case RELEASE:
lockCount--;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
if (lockCount == 0) {
// remove from fast cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
// atomic because may collide with concurrent ACQUIRE
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
#endif
// Check per-thread cache of already-owned locks for matching object
//第二部分
//part2: 不是第一次进来,而且不是同一线程
//在全局线程缓存中查找
SyncCache *cache = fetch_cache(NO);
if (cache) {
unsigned int i;
for (i = 0; i < cache->used; i++) {
SyncCacheItem *item = &cache->list[i];
if (item->data->object != object) continue;
// Found a match.
result = item->data;
if (result->threadCount <= 0 || item->lockCount <= 0) {
_objc_fatal("id2data cache is buggy");
}
switch(why) {
case ACQUIRE:
item->lockCount++;
break;
case RELEASE:
item->lockCount--;
if (item->lockCount == 0) {
// remove from per-thread cache
cache->list[i] = cache->list[--cache->used];
// atomic because may collide with concurrent ACQUIRE
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
// Thread cache didn't find anything.
// Walk in-use list looking for matching object
// Spinlock prevents multiple threads from creating multiple
// locks for the same new object.
// We could keep the nodes in some hash table if we find that there are
// more than 20 or so distinct locks active, but we don't do that now.
//第三部分
lockp->lock();
//park3:缓存中没有发现,没有加锁,此第一次加锁
{
SyncData* p;
SyncData* firstUnused = NULL;
//循环listp是否有值
for (p = *listp; p != NULL; p = p->nextData) {
if ( p->object == object ) {
result = p;
// atomic because may collide with concurrent RELEASE
OSAtomicIncrement32Barrier(&result->threadCount);
goto done;
}
if ( (firstUnused == NULL) && (p->threadCount == 0) )
firstUnused = p;
}
// 如果SyncData为空,进入done中执行
// no SyncData currently associated with object
if ( (why == RELEASE) || (why == CHECK) )
goto done;
// an unused one was found, use it
//SyncData不为空,将threadCount置位1
if ( firstUnused != NULL ) {
result = firstUnused;
result->object = (objc_object *)object;
result->threadCount = 1;
goto done;
}
}
// Allocate a new SyncData and add to list.
// XXX allocating memory with a global lock held is bad practice,
// might be worth releasing the lock, allocating, and searching again.
// But since we never free these guys we won't be stuck in allocation very often.
posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));
result->object = (objc_object *)object;
result->threadCount = 1;
new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
result->nextData = *listp;
*listp = result;
done:
lockp->unlock();
if (result) {
// Only new ACQUIRE should get here.
// All RELEASE and CHECK and recursive ACQUIRE are
// handled by the per-thread caches above.
if (why == RELEASE) {
// Probably some thread is incorrectly exiting
// while the object is held by another thread.
return nil;
}
if (why != ACQUIRE) _objc_fatal("id2data is buggy");
if (result->object != object) _objc_fatal("id2data is buggy");
#if SUPPORT_DIRECT_THREAD_KEYS
if (!fastCacheOccupied) {
// Save in fast thread cache
//向tls中设置值,将result和lockCount都设置进去
tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
} else
#endif
{
// Save in thread cache
//线程缓存中也存一份
if (!cache) cache = fetch_cache(YES);
cache->list[cache->used].data = result;
cache->list[cache->used].lockCount = 1;
cache->used++;
}
}
return result;
}
done流程
-
判断是否支持
SUPPORT_DIRECT_THREAD_KEYS
,如果支持执行KVC
,就向tls
中设置值,- 将
result
设置进去,SYNC_DATA_DIRECT_KEY
lockCount = 1
设置到,SYNC_COUNT_DIRECT_KEY
- 将
-
不支持
SUPPORT_DIRECT_THREAD_KEYS
,那么就向线程缓存
中存一份
done:
lockp->unlock();
if (result) {
// Only new ACQUIRE should get here.
// All RELEASE and CHECK and recursive ACQUIRE are
// handled by the per-thread caches above.
if (why == RELEASE) {
// Probably some thread is incorrectly exiting
// while the object is held by another thread.
return nil;
}
if (why != ACQUIRE) _objc_fatal("id2data is buggy");
if (result->object != object) _objc_fatal("id2data is buggy");
#if SUPPORT_DIRECT_THREAD_KEYS
if (!fastCacheOccupied) {
// Save in fast thread cache
//向tls中设置值,将result和lockCount都设置进去
tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
} else
#endif
{
// Save in thread cache
//线程缓存中也存一份
if (!cache) cache = fetch_cache(YES);
cache->list[cache->used].data = result;
cache->list[cache->used].lockCount = 1;
cache->used++;
}
}
return result;
第一种情况:第一次进来没有加锁
执行第三部分
,在执行第四部分
-
第一次
进来,在两个缓存中都没有找到
,那么就会执行第三部分
的流程- 循环listp是否有值,如果
有
值就将此值赋给resul
t,并对threadCount++
,跳转到done
流程(显然第一次进入时没有值的哦) - 如果
SyncData
为空
,进入done
中执行 SyncData
不
为空
,第一次进来没有找到,将threadCount
置位1
- 循环listp是否有值,如果
lockp->lock();
//park3:缓存中没有发现,没有加锁,此第一次加锁
{
SyncData* p;
SyncData* firstUnused = NULL;
//循环listp是否有值
for (p = *listp; p != NULL; p = p->nextData) {
if ( p->object == object ) {
result = p;
// atomic because may collide with concurrent RELEASE
OSAtomicIncrement32Barrier(&result->threadCount);
goto done;
}
//没有找到就将p赋值给firstUnused
if ( (firstUnused == NULL) && (p->threadCount == 0) )
firstUnused = p;
}
// 如果SyncData为空,进入done中执行
// no SyncData currently associated with object
if ( (why == RELEASE) || (why == CHECK) )
goto done;
// an unused one was found, use it
//SyncData不为空,将threadCount置位1
if ( firstUnused != NULL ) {
result = firstUnused;
result->object = (objc_object *)object;
result->threadCount = 1;
goto done;
}
}
// Allocate a new SyncData and add to list.
// XXX allocating memory with a global lock held is bad practice,
// might be worth releasing the lock, allocating, and searching again.
// But since we never free these guys we won't be stuck in allocation very often.
posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));
result->object = (objc_object *)object;
result->threadCount = 1;
new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
result->nextData = *listp;
*listp = result;
第二种情况:不是第一次进来,但是是同一个线程
执行第一部分
-
在
TLS
线程局部存储缓存(栈
)中查找是否存在SyncData
-
获取lockCount,如果threadCount 和 lockCount都小于等于0,抛出异常
-
判断
why
是ACQUIRE
还是RELEASE
-
ACQUIRE
:lockCount++
-
RELEASE
:lockCount--
-
-
TLS
再存一下
//part1: 不是第一次进来,但是在同一个线程
//在TLS线程局部存储缓存(栈)中查找是否存在SyncData
bool fastCacheOccupied = NO;
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
if (data) {
fastCacheOccupied = YES;
if (data->object == object) {
// Found a match in fast cache.
uintptr_t lockCount;
result = data;
//获取lockCount
lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
//如果threadCount 和 lockCount都小于等于0,抛出异常
if (result->threadCount <= 0 || lockCount <= 0) {
_objc_fatal("id2data fastcache is buggy");
}
//why => 外部传入的ACQUIRE或者RELEASE
switch(why) {
//ACQUIRE lockCount + 1
case ACQUIRE: {
lockCount++;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
break;
}
//RELEASE lockCount - 1
case RELEASE:
lockCount--;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
if (lockCount == 0) {
// remove from fast cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
// atomic because may collide with concurrent ACQUIRE
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
第三种情况:不是第一次进来,也不是同一个线程
先执行第二部分
,在执行第三部分
,最后执行第四部分
- 在
全局线程缓存
中查找,找到item,将lockCount++
- 再执行
第三部分
,将threadCount++
- 最后执行
第四部分
。
//第二部分
//part2: 不是第一次进来,而且不是同一线程
//在全局线程缓存中查找
SyncCache *cache = fetch_cache(NO);
if (cache) {
unsigned int i;
for (i = 0; i < cache->used; i++) {
SyncCacheItem *item = &cache->list[i];
if (item->data->object != object) continue;
// Found a match.
result = item->data;
if (result->threadCount <= 0 || item->lockCount <= 0) {
_objc_fatal("id2data cache is buggy");
}
switch(why) {
case ACQUIRE:
item->lockCount++;
break;
case RELEASE:
item->lockCount--;
if (item->lockCount == 0) {
// remove from per-thread cache
cache->list[i] = cache->list[--cache->used];
// atomic because may collide with concurrent ACQUIRE
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
part2: 为空执行objc_sync_nil
- 查看
objc_sync_nil
,进入BREAKPOINT_FUNCTION
,传入参数是void objc_sync_nil(void)
BREAKPOINT_FUNCTION(
void objc_sync_nil(void)
);
👇
- 查看
BREAKPOINT_FUNCTION
,传入参数prototype
,而它执行了asm("");
什么都没操作。
# define BREAKPOINT_FUNCTION(prototype) \
OBJC_EXTERN __attribute__((noinline, used, visibility("hidden"))) \
prototype { asm(""); }
1.3 坑点
- 下面代码这样写,会有什么问题?
- (void)cjl_testSync{
_testArray = [NSMutableArray array];
for (int i = 0; i < 200000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@synchronized (self.testArray) {
self.testArray = [NSMutableArray array];
}
});
}
}
- 运行结果是
程序崩溃
崩溃的主要原因是testArray在某一瞬间变成了nil,从@synchronized底层流程知道,如果加锁的对象成了nil,是锁不住的,相当于下面这种情况,block内部不停的retain、release,会在某一瞬间上一个还未release,下一个已经准备release,这样会导致野指针的产生
- 可以根据下面的代码,打开edit scheme -> run -> Diagnostics中勾选Zombie Objects,来查看是否是僵尸对象,结果如下所示
_testArray = [NSMutableArray array];
for (int i = 0; i < 200000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
_testArray = [NSMutableArray array];
});
}
- 我们一般使用
@synchronized (self)
,主要是因为_testArray
的持有者是self
注意:野指针 vs 过渡释放
-
野指针
:是指由于过渡释放产生的指针还在进行操作 -
过渡释放
:每次都会retain 和 release
1.4 小结
- 由于
@synchronized
,在加锁时,查询了两张表,所以比较耗费性能
。 - 全局加锁线程缓存:存储的所有的
加锁线程
,根据’对象’找到’加锁对象’。 TLS
线程局部存储缓存(栈
):存储的是加锁的对象
(必须
是同
一个对象
)- 使用
方便
,易写
。 - tls和cache表结构
2. atomic 原子锁 & nonatomic 非原子锁
atomic 和 nonatomic主要用于属性的修饰,以下是相关的一些说明:
-
atomic是
原子属性
,是为多线程
开发准备的,是默认属性
!-
仅仅在属性的
setter
方法中,增加了锁(自旋锁)
,能够保证同一时间,只有一条
线程对属性进行写
操作 -
同一时间
单(线程)写
多(线程)读
的线程处理技术 -
Mac开发中常用
-
-
nonatomic 是
非原子属性
-
没有锁!性能高!
-
移动端开发常用
-
atomic与nonatomic 的区别
-
nonatomic
-
非原子
属性 -
非线程安全
,适合内存小的移动设备
-
-
atomic
-
原子
属性(线程安全),针对多线程
设计的,默认值 -
保证
同一时间只有一个线程能够写入
(但是同一个时间多个线程都可以取值) -
atomic 本身就有一把
锁(自旋锁) 单写多读
:单个线程写入,多个线程可以读取 -
线程安全
,需要消耗大量的资源
-
-
iOS 开发的建议
-
所有属性都声明为
nonatomic
-
尽量避免多线程抢夺同一块资源
尽量将加锁、资源抢夺的业务逻辑交给服务器端处理
,减小移动客户端的压力
-
3. 信号量
请查看文章OC底层探索(二十一)GCD异步、GCD同步、单例、信号量、调度组、栅栏函数等底层分析
4. NSLock
4.1 NSLock的使用
NSLock 是一把互斥锁,不可在
递归循环
中使用
// NSLock 多 非常简单
NSLock *lock = [[NSLock alloc] init];
[lock lock];
[lock unlock];
4.2 NSLock源码
由于NSLock是在foundation中,OC是没有开源的
所以看swift源码即可:
NSLock是对
pthread
的封装
lock
: 直接调用互斥锁
unlock
: 调用解锁
,并将当前解锁的状态广播
出去NSLock
中的init
方法中,还做了一些其他操作,所以在使用NSLock时需要使用init初始化
5、NSRecursiveLock
NSRecursiveLock 是递归互斥锁
5.1 NSRecursiveLock 的使用
NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
[recursiveLock lock];
[recursiveLock unlock];
5.2 NSRecursiveLock 源码
NSRecursiveLock
在底层也是对pthread_mutex
的封装,可以通过swift的Foundation源码查看
- 对比
NSLock
和NSRecursiveLock
,其底层实现几乎一模一样,区别在于init
时,NSRecursiveLock
有一个标识PTHREAD_MUTEX_RECURSIVE
,而NSLock
是默认
的
递归锁
主要是用于解决一种嵌套形
式,其中循环嵌套
居多
6. NSCondition
消费者
- 生产者
模式加锁
-NSCondition
是一个条件锁
,在日常开发中使用较少,与信号量
有点相似:线程1
需要满足
条件1才会往下走,否则会堵塞等待
,知道条件满足。经典模型是生产消费者模型
-
NSCondition
的对象实际上作为一个锁
和一个线程检查器
-
锁
主要 为了当检测条件时保护数据源
,执行条件引发的任务 -
线程检查器
主要是根据条件决定是否继续
运行线程,即线程是否被阻塞
6.1 NSCondition的使用
- (void)lg_testConditon{
_testCondition = [[NSCondition alloc] init];
//创建生产-消费者
for (int i = 0; i < 50; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_producer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self lg_producer];
});
}
}
- (void)lg_producer{
[_testCondition lock]; // 操作的多线程影响
self.ticketCount = self.ticketCount + 1;
NSLog(@"生产一个 现有 count %zd",self.ticketCount);
[_testCondition signal]; // 信号
[_testCondition unlock];
}
- (void)lg_consumer{
[_testCondition lock]; // 操作的多线程影响
if (self.ticketCount == 0) {
NSLog(@"等待 count %zd",self.ticketCount);
[_testCondition wait];
}
//注意消费行为,要在等待条件判断之后
self.ticketCount -= 1;
NSLog(@"消费一个 还剩 count %zd ",self.ticketCount);
[_testCondition unlock];
}
6.2 源码分析
通过swift的Foundation源码查看NSCondition的底层实现
-
其底层也是对下层
pthread_mutex
的封装 -
NSCondition
是对mutex
和cond
的一种封装(cond就是用于访问和操作特定类型数据的指针) -
wait
操作会阻塞
线程,使其进入休眠
状态,直至超时
-
signal
操作是唤醒一个
正在休眠等待的线程 -
broadcast
会唤醒所有
正在等待的线程
open class NSCondition: NSObject, NSLocking {
internal var mutex = _MutexPointer.allocate(capacity: 1)
internal var cond = _ConditionVariablePointer.allocate(capacity: 1)
//初始化
public override init() {
pthread_mutex_init(mutex, nil)
pthread_cond_init(cond, nil)
}
//析构
deinit {
pthread_mutex_destroy(mutex)
pthread_cond_destroy(cond)
mutex.deinitialize(count: 1)
cond.deinitialize(count: 1)
mutex.deallocate()
cond.deallocate()
}
//加锁
open func lock() {
pthread_mutex_lock(mutex)
}
//解锁
open func unlock() {
pthread_mutex_unlock(mutex)
}
//等待
open func wait() {
pthread_cond_wait(cond, mutex)
}
//等待
open func wait(until limit: Date) -> Bool {
guard var timeout = timeSpecFrom(date: limit) else {
return false
}
return pthread_cond_timedwait(cond, mutex, &timeout) == 0
}
//信号,表示等待的可以执行了
open func signal() {
pthread_cond_signal(cond)
}
//广播
open func broadcast() {
// 汇编分析
pthread_cond_broadcast(cond) // wait signal
}
open var name: String?
}
7、NSConditionLock
NSConditionLock
是条件锁
,一旦一个线程
获得锁,其他线程一定等待
相比NSConditionLock
而言,NSCondition
使用比较麻烦
,所以推荐使用NSConditionLock
,其使用如下
NSConditionLock的使用
NSConditionLock
的API
//初始化
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
//表示 conditionLock 期待获得锁,如果没有其他线程获得锁(不需要判断内部的 condition) 那它能执行此行以下代码,如果已经有其他线程获得锁(可能是条件锁,或者无条件 锁),则等待,直至其他线程解锁
[conditionLock lock];
//表示如果没有其他线程获得该锁,但是该锁内部的 condition不等于A条件,它依然不能获得锁,仍然等待。如果内部的condition等于A条件,并且 没有其他线程获得该锁,则进入代码区,同时设置它获得该锁,其他任何线程都将等待它代码的 完成,直至它解锁。
[conditionLock lockWhenCondition:A条件];
//表示释放锁,同时把内部的condition设置为A条件
[conditionLock unlockWithCondition:A条件];
// 表示如果被锁定(没获得 锁),并超过该时间则不再阻塞线程。但是注意:返回的值是NO,它没有改变锁的状态,这个函 数的目的在于可以实现两种状态下的处理
return = [conditionLock lockWhenCondition:A条件 beforeDate:A时间];
//其中所谓的condition就是整数,内部通过整数比较条件
例子
- (void)lg_testConditonLock{
// 信号量
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[conditionLock lockWhenCondition:1]; // conditoion = 1 内部 Condition 匹配
// -[NSConditionLock lockWhenCondition: beforeDate:]
NSLog(@"线程 1");
[conditionLock unlockWithCondition:0];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[conditionLock lockWhenCondition:2];
sleep(0.1);
NSLog(@"线程 2");
// self.myLock.value = 1;
[conditionLock unlockWithCondition:1]; // _value = 2 -> 1
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[conditionLock lock];
NSLog(@"线程 3");
[conditionLock unlock];
});
}
-
线程 1
调用[NSConditionLock lockWhenCondition:]
,此时此刻因为不满足
当前条件,所以会进入
waiting
状态,当前进入到waiting
时,会释放
当前的互斥锁
。 -
此时当前的
线程 3
调用[NSConditionLock lock:]
,本质上是调用[NSConditionLock lockBeforeDate:]
,这里不需要
比对条件值,所以线程 3
会打印
-
接下来
线程 2
执行[NSConditionLock lockWhenCondition:]
,因为满足
条件值,所以线程2
会打印,打印完成后会调用[NSConditionLock unlockWithCondition:]
,这个时候将value
设置为
1
,并发送boradcast
, 此时线程 1
接收到当前的信号,唤醒
执行并打印。 -
自此当前打印为
线程 3
->线程 2
->线程 1
-
[NSConditionLock lockWhenCondition:]
;这里会根据传入的condition
值和Value
值进行对比,如果不相等
,这里就会阻塞
,进入线程池,否则的话就继续代码执行[NSConditionLock unlockWithCondition:]:
这里会先更改当前的value
值,然后进行广播
,唤醒当前的线程
7.2 源码分析
-NSConditionLock
是NSCondition
的封装
-
NSConditionLock
可以设置锁条件
,即condition
值,而NSCondition
只是信号
的通知 -
当
condition
与value
不相等
时,线程等待
,超时解锁
;反之,解锁
open class NSConditionLock : NSObject, NSLocking {
internal var _cond = NSCondition()
internal var _value: Int
internal var _thread: _swift_CFThreadRef?
public convenience override init() {
self.init(condition: 0)
}
public init(condition: Int) {
_value = condition
}
open func lock() {
let _ = lock(before: Date.distantFuture)
}
open func unlock() {
_cond.lock()
_thread = nil
_cond.broadcast()
_cond.unlock()
}
open var condition: Int {
return _value
}
open func lock(whenCondition condition: Int) {
let _ = lock(whenCondition: condition, before: Date.distantFuture)
}
open func `try`() -> Bool {
return lock(before: Date.distantPast)
}
open func tryLock(whenCondition condition: Int) -> Bool {
return lock(whenCondition: condition, before: Date.distantPast)
}
open func unlock(withCondition condition: Int) {
_cond.lock()
_thread = nil
_value = condition
_cond.broadcast()
_cond.unlock()
}
open func lock(before limit: Date) -> Bool {
_cond.lock()
while _thread != nil {
if !_cond.wait(until: limit) {
_cond.unlock()
return false
}
}
_thread = pthread_self()
_cond.unlock()
return true
}
open func lock(whenCondition condition: Int, before limit: Date) -> Bool {
_cond.lock()
while _thread != nil || _value != condition {
if !_cond.wait(until: limit) {
_cond.unlock()
return false
}
}
_thread = pthread_self()
_cond.unlock()
return true
}
open var name: String?
}
8.pthread_mutex
pthread_mutex
就是互斥锁
本身,当锁被占用,其他线程申请锁时,不会一直忙等待,而是阻塞线程并睡眠
- 使用
// 导入头文件
#import <pthread.h>
// 全局声明互斥锁
pthread_mutex_t _lock;
// 初始化互斥锁
pthread_mutex_init(&_lock, NULL);
// 加锁
pthread_mutex_lock(&_lock);
// 这里做需要线程安全操作
// 解锁
pthread_mutex_unlock(&_lock);
// 释放锁
pthread_mutex_destroy(&_lock);
总结
-
OSSpinLock
自旋锁由于安全性
问题,在iOS10之后
已经被废弃
,其底层的实现用os_unfair_lock
替代-
使用
OSSpinLock
及所示,会处于忙等待
状态 -
而
os_unfair_lock
是处于休眠
状态
-
-
atomic
原子锁自带一把自旋锁
,只能保证sette
r、getter
时的线程安全,在日常开发中使用更多的还是nonatomic
修饰属性-
atomic
:当属性在调用setter
、getter
方法时,会加上自旋锁osspinlock
,用于保证同一时刻
只能有一个线程
调用属性的读或写
,避免
了属性
读写不同步
的问题。由于是底层编译器
自动生成的互斥锁
代码,会导致效率
相对较低
-
nonatomic
:当属性在调用setter
、getter
方法时,不会
加上自旋锁,即线程不安全
。由于编译器不会自动生成互斥锁代码,可以提高
效率
-
-
@synchronized
在底层维护了一个哈希表
进行线程data
的存储,通过链表
表示可重入
(即嵌套)的特性,虽然性能较低
,但由于简单好用,使用频率很高
-
NSLock
、NSRecursiveLock
底层是对pthread_mutex
的封装 -
NSCondition
和NSConditionLock
是条件锁
,底层都是对pthread_mutex
的封装,当满足某一个
条件时才能进行操作,和信号量dispatch_semaphore
类似
使用场景
-
如果只是
简单
的使用,例如涉及线程安全
,使用NSLock
即可 -
如果是
循环嵌套
,推荐
使用@synchronized
,主要是因为使用递归锁的性能
不如使用@synchronized的性能(因为在synchronized中无论怎么重入,都没有关系,而NSRecursiveLock
可能会出现崩溃现象) -
在
循环嵌套
中,如果对递归锁掌握的很好
,则建议使用递归锁
,因为性能好 -
如果是
循环嵌套
,并且还有多线程影响
时,例如有等待
、死锁
现象时,建议使用@synchronized