iOS中的一些锁

多线程在日常开发中能起到性能优化的作用,但是一旦没用好就会造成线程不安全,本文就来讲讲如何保证线程安全

线程安全

当一个线程访问数据的时候,其他的线程不能对其进行访问,直到该线程访问完毕。简单来讲就是在同一时刻,对同一个数据操作的线程只有一个。而线程不安全,则是在同一时刻可以有多个线程对该数据进行访问,从而得不到预期的结果
即线程内操作了一个线程外的非线程安全变量,这个时候一定要考虑线程安全和同步

锁的作用

作为一种非强制的机制,被用来保证线程安全。每一个线程在访问数据或者资源前,要先获取(Acquire)锁,并在访问结束之后释放(Release)锁。如果锁已经被占用,其它试图获取锁的线程会等待,直到锁重新可用
注:不要将过多的其他操作代码放到锁里面,否则一个线程执行的时候另一个线程就一直在等待,就无法发挥多线程的作用了

锁的分类

在iOS中锁的基本种类只有两种:互斥锁自旋锁,其他的比如条件锁、递归锁、信号量都是上层的封装和实现

互斥锁

互斥锁(Mutual exclusion,缩写Mutex)防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒
互斥锁又分为:

  • 递归锁:可重入锁,同一个线程在锁释放前可再次获取锁,即可以递归调用
  • 非递归锁:不可重入,必须等锁释放后才能再次获取锁

自旋锁

自旋锁:线程反复检查锁变量是否可⽤。由于线程在这⼀过程中保持执⾏, 因此是⼀种忙等待。⼀旦获取了⾃旋锁,线程会⼀直保持该锁,直⾄显式释 放⾃旋锁

⾃旋锁避免了进程上下⽂的调度开销,因此对于线程只会阻塞很短时间的场合是有效的

互斥锁和自旋锁的区别

  • 互斥锁在线程获取锁但没有获取到时,线程会进入休眠状态,等锁被释放时线程会被唤醒
  • 自旋锁的线程则会一直处于等待状态(忙等待)不会进入休眠——因此效率高

自旋锁

OSSpinLock

自从OSSpinLock出现了安全问题之后就废弃了。自旋锁之所以不安全,是因为自旋锁由于获取锁时,线程会一直处于忙等待状态,造成了任务的优先级反转
OSSpinLock忙等的机制就可能造成高优先级一直running等待,占用CPU时间片;而低优先级任务无法抢占时间片,变成迟迟完不成,不释放锁的情况

atomic

atomic原理

在iOS探索 KVC原理及自定义中有提到自动生成的setter方法会根据修饰符不同调用不同方法,最后统一调用reallySetProperty方法,其中就有一段关于atomic修饰词的代码

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{if (offset == 0) {object_setClass(self, newValue);return;}id oldValue;id *slot = (id*) ((char*)self + offset);if (copy) {newValue = [newValue copyWithZone:nil];} else if (mutableCopy) {newValue = [newValue mutableCopyWithZone:nil];} else {if (*slot == newValue) return;newValue = objc_retain(newValue);}if (!atomic) {oldValue = *slot;*slot = newValue;} else {spinlock_t& slotlock = PropertyLocks[slot];slotlock.lock();oldValue = *slot;*slot = newValue;        slotlock.unlock();}objc_release(oldValue);
}

比对一下atomic的逻辑分支:

原子性修饰的属性进行了spinlock加锁处理
非原子性的属性除了没加锁,其他逻辑与atomic一般无二

等等,前面不是刚说OSSpinLock因为安全问题被废弃了吗,但是苹果源码怎么还在使用呢?其实点进去就会发现用os_unfair_lock替代了OSSpinLock(iOS10之后替换)

using spinlock_t = mutex_tt<LOCKDEBUG>;class mutex_tt : nocopy_t {os_unfair_lock mLock;...
}

同时为了哈希不冲突,还使用加盐操作进行加锁

getter方法亦是如此:atomic修饰的属性进行加锁处理

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {if (offset == 0) {return object_getClass(self);}// Retain release worldid *slot = (id*) ((char*)self + offset);if (!atomic) return *slot;// Atomic retain release worldspinlock_t& slotlock = PropertyLocks[slot];slotlock.lock();id value = objc_retain(*slot);slotlock.unlock();// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.return objc_autoreleaseReturnValue(value);
}

atomic修饰的属性绝对安全吗?

atomic只能保证setter、getter方法的线程安全,并不能保证数据安全

//#import "ViewController.h"
#import "os/lock.h"
@interface ViewController ()
@property (atomic, assign) NSInteger index;
@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];self.index = 0;dispatch_async(dispatch_get_global_queue(0, 0), ^{for (int i = 0; i < 10000; i++) {self.index = self.index + 1;NSLog(@"%d--%ld", i , (long)self.index);}});dispatch_async(dispatch_get_global_queue(0, 0), ^{for (int i = 0; i < 10000; i++) {self.index = self.index + 1;NSLog(@"%d--%ld", i, (long)self.index);}});//[self ticketTest];// Do any additional setup after loading the view.
}

请添加图片描述
如上图所示,被atomic修饰的index变量分别在两次并发异步for循环10000次后输出的结果并不等于20000。由此可以得出结论:

  • atomic保证变量在取值和赋值时的线程安全
  • 但不能保证self.index+1也是安全的
  • 如果改成self.index=i是能保证setter方法的线程安全的

请添加图片描述

读写锁

读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的CPU

  • 写者是排他性的,⼀个读写锁同时只能有⼀个写者或多个读者(与CPU数相关),但不能同时既有读者⼜有写者。在读写锁保持期间也是抢占失效的
  • 如果读写锁当前没有读者,也没有写者,那么写者可以⽴刻获得读写锁,否则它必须⾃旋在那⾥,直到没有任何写者或读者。如果读写锁没有写者,那么读者可以⽴即获得该读写锁,否则读者必须⾃旋在那⾥,直到写者释放该读写锁
// 导入头文件
#import <pthread.h>
// 全局声明读写锁
pthread_rwlock_t lock;
// 初始化读写锁
pthread_rwlock_init(&lock, NULL);
// 读操作-加锁
pthread_rwlock_rdlock(&lock);
// 读操作-尝试加锁
pthread_rwlock_tryrdlock(&lock);
// 写操作-加锁
pthread_rwlock_wrlock(&lock);
// 写操作-尝试加锁
pthread_rwlock_trywrlock(&lock);
// 解锁
pthread_rwlock_unlock(&lock);
// 释放锁
pthread_rwlock_destroy(&lock);

平时很少会直接使用读写锁pthread_rwlock_t,更多的是采用其他方式,例如使用栅栏函数完成读写锁的需求

互斥锁

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);

YYKit的YYMemoryCach有使用到pthread_mutex

@synchronized

@synchronized可能是日常开发中用的比较多的一种互斥锁,因为它的使用比较简单,但并不是在任意场景下都能使用@synchronized,且它的性能较低

@synchronized (obj) {}

接下来就通过源码探索来看一下@synchronized在使用中的注意事项

  • 通过汇编能发现@synchronized就是实现了objc_sync_enterobjc_sync_exit两个方法
  • 通过符号断点能知道这两个方法都是在objc源码中的
  • 通过clang也能得到一些信息:
int main(int argc, char * argv[]) {NSString * appDelegateClassName;/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));{id _rethrow = 0;id _sync_obj = (id)appDelegateClassName;objc_sync_enter(_sync_obj);try {struct _SYNC_EXIT {_SYNC_EXIT(id arg) : sync_exit(arg) {}~_SYNC_EXIT() {objc_sync_exit(sync_exit);}id sync_exit;}_sync_exit(_sync_obj);}catch (id e) {_rethrow = e;}{struct _FIN { _FIN(id reth) : rethrow(reth) {}~_FIN() { if (rethrow) objc_exception_throw(rethrow); }id rethrow;}_fin_force_rethow(_rethrow);}}}return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}

源码分析

在objc源码中找到objc_sync_enterobjc_sync_exit

// 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 nothingif (DebugNilSync) {_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");}objc_sync_nil();}return result;
}// End synchronizing on 'obj'. 
// Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
int objc_sync_exit(id obj)
{int result = OBJC_SYNC_SUCCESS;if (obj) {SyncData* data = id2data(obj, RELEASE); if (!data) {result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;} else {bool okay = data->mutex.tryUnlock();if (!okay) {result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;}}} else {// @synchronized(nil) does nothing}return result;
}
  • 首先从它的注释中recursive mutex可以得出@synchronized是递归锁
  • 如果锁的对象obj不存在时分别会走objc_sync_nil()不做任何操作(源码分析可以先解决简单的逻辑分支)
BREAKPOINT_FUNCTION(void objc_sync_nil(void)
);

这也是@synchronized作为递归锁但能防止死锁的原因所在:在不断递归的过程中如果对象不存在了就会停止递归从而防止死锁

正常情况下(obj存在)会通过id2data方法生成一个SyncData对象

  • nextData指的是链表中下一个SyncData
  • object指的是当前加锁的对象
  • threadCount表示使用该对象进行加锁的线程数
  • mutex即对象所关联的锁
typedef struct alignas(CacheLineSize) SyncData {struct SyncData* nextData;DisguisedPtr<objc_object> object;int32_t threadCount;  // number of THREADS using this blockrecursive_mutex_t mutex;
} SyncData;

准备SyncData

static SyncData* id2data(id object, enum usage why)
{spinlock_t *lockp = &LOCK_FOR_OBJ(object);SyncData **listp = &LIST_FOR_OBJ(object);SyncData* result = NULL;...
}

id2data先将返回对象SyncData类型的result准备好,后续进行数据填充

#define LOCK_FOR_OBJ(obj) sDataLists[obj].lock
#define LIST_FOR_OBJ(obj) sDataLists[obj].datastatic StripedMap<SyncList> sDataLists;struct SyncList {SyncData *data;spinlock_t lock;constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { }
};

其中通过两个宏定义去取得SyncList中的datalock——static StripedMap<SyncList> sDataLists 可以理解成 NSArray<id> list
既然@synchronized能在任意地方(VC、View、Model等)使用,那么底层必然维护着一张全局的表(类似于weak表)。而从SyncListSyncData的结构可以证实系统确实在底层维护着一张哈希表,里面存储着SyncList结构的数据。SyncListSyncData的关系如下图所示:
在这里插入图片描述

使用快速缓存

static SyncData* id2data(id object, enum usage why)
{...
#if SUPPORT_DIRECT_THREAD_KEYS// Check per-thread single-entry fast cache for matching object// 检查每线程单项快速缓存中是否有匹配的对象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 = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);if (result->threadCount <= 0  ||  lockCount <= 0) {_objc_fatal("id2data fastcache is buggy");}switch(why) {case ACQUIRE: {lockCount++;tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);break;}case RELEASE:lockCount--;tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);if (lockCount == 0) {// remove from fast cachetls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);// atomic because may collide with concurrent ACQUIREOSAtomicDecrement32Barrier(&result->threadCount);}break;case CHECK:// do nothingbreak;}return result;}}
#endif...
}

这里有个重要的知识点——TLSTLS全称为Thread Local Storage,在iOS中每个线程都拥有自己的TLS,负责保存本线程的一些变量, 且TLS无需锁保护
快速缓存的含义为:定义两个变量SYNC_DATA_DIRECT_KEY/SYNC_COUNT_DIRECT_KEY,与tsl_get_direct/tls_set_direct配合可以从线程局部缓存中快速取得SyncCacheItem.dataSyncCacheItem.lockCount
如果在缓存中找到当前对象,就拿出当前被锁的次数lockCount,再根据传入参数类型(获取、释放、查看)对lockCount分别进行操作

  • 获取资源ACQUIRElockCount++并根据key值存入被锁次数
  • 释放资源RELEASElockCount++并根据key值存入被锁次数。如果次数变为0,此时锁也不复存在,需要从快速缓存移除并清空线程数threadCount
  • 查看资源check:不操作

lockCount表示被锁的次数,意味着能多次进入,从侧面表现出了递归性

获取该线程下的SyncCache

这个逻辑分支是找不到确切的线程标记只能进行所有的缓存遍历

static SyncData* id2data(id object, enum usage why)
{...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 cachecache->list[i] = cache->list[--cache->used];// atomic because may collide with concurrent ACQUIREOSAtomicDecrement32Barrier(&result->threadCount);}break;case CHECK:// do nothingbreak;}return result;}}...
}

这里介绍一下SyncCacheSyncCacheItem

typedef struct {SyncData *data;             //该缓存条目对应的SyncDataunsigned int lockCount;     //该对象在该线程中被加锁的次数
} SyncCacheItem;typedef struct SyncCache {unsigned int allocated;     //该缓存此时对应的缓存大小unsigned int used;          //该缓存此时对应的已使用缓存大小SyncCacheItem list[0];      //SyncCacheItem数组
} SyncCache;
  • SyncCacheItem用来记录某个SyncData在某个线程中被加锁的记录,一个SyncData可以被多个SyncCacheItem持有
  • SyncCache用来记录某个线程中所有SyncCacheItem,并且记录了缓存大小以及已使用缓存大小

全局哈希表查找

快速、慢速流程都没找到缓存就会来到这步——在系统保存的哈希表进行链式查找

static SyncData* id2data(id object, enum usage why)
{...lockp->lock();{SyncData* p;SyncData* firstUnused = NULL;for (p = *listp; p != NULL; p = p->nextData) {if ( p->object == object ) {result = p;// atomic because may collide with concurrent RELEASEOSAtomicIncrement32Barrier(&result->threadCount);goto done;}if ( (firstUnused == NULL) && (p->threadCount == 0) )firstUnused = p;}// no SyncData currently associated with objectif ( (why == RELEASE) || (why == CHECK) )goto done;// an unused one was found, use itif ( firstUnused != NULL ) {result = firstUnused;result->object = (objc_object *)object;result->threadCount = 1;goto done;}}...
}
  • lockp->lock()并不是在底层对锁进行了封装,而是在查找过程前后进行了加锁操作
  • for循环遍历链表,如果有符合的就goto done
  • 寻找链表中未使用的SyncData并作标记
  • 如果是RELEASECHECK直接goto done
  • 如果第二步中有发现第一次使用的的对象就将threadCount标记为1且goto done

生成新数据并写入缓存

static SyncData* id2data(id object, enum usage why)
{...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_KEYSif (!fastCacheOccupied) {// Save in fast thread cachetls_set_direct(SYNC_DATA_DIRECT_KEY, result);tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);} else 
#endif{// Save in thread cacheif (!cache) cache = fetch_cache(YES);cache->list[cache->used].data = result;cache->list[cache->used].lockCount = 1;cache->used++;}}...
}
  • 第三步情况均不满足(即链表不存在——对象对于全部线程来说是第一次加锁)就会创建SyncData并存在result里,方便下次进行存储
  • done分析:
    • 先将前面的lock锁解开
    • 如果是RELEASE类型直接返回nil
    • 对ACQUIRE类型和对象的断言判断
    • !fastCacheOccupied分支表示支持快速缓存且快速缓存被占用了,将该SyncCacheItem数据写入快速缓存中
    • 否则将该SyncCacheItem存入该线程对应的SyncCache中

疑难解答

  • 不能使用非OC对象作为加锁条件——id2data中接收参数为id类型
  • 多次锁同一个对象会有什么后果吗——会从高速缓存中拿到data,所以只会锁一次对象
  • 都说@synchronized性能低——是因为在底层增删改查消耗了大量性能
  • 加锁对象不能为nil,否则加锁无效,不能保证线程安全
- (void)test {_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,但哈希表中存的对象也变成了nil,导致synchronized无效化

解决方案:

  • self进行同步锁,这个似乎太臃肿了
  • 使用NSLock

NSLock

使用

NSLock是对互斥锁的简单封装,使用如下:

- (void)test {self.testArray = [NSMutableArray array];NSLock *lock = [[NSLock alloc] init];for (int i = 0; i < 200000; i++) {dispatch_async(dispatch_get_global_queue(0, 0), ^{[lock lock];self.testArray = [NSMutableArray array];[lock unlock];});}
}

NSLock在AFNetworking的AFURLSessionManager.m中有使用到
想要了解一下NSLock的底层原理,但发现其是在未开源的Foundation源码下面的,但但是Swift对Foundation却开源了,可以在swift-corelibs-foundation下载到源码来一探究竟
请添加图片描述

注意事项

使用互斥锁NSLock异步并发调用block块,block块内部递归调用自己,问打印什么

- (void)test {NSLock *lock = [[NSLock alloc] init];dispatch_async(dispatch_get_global_queue(0, 0), ^{static void (^block)(int);block = ^(int value) {NSLog(@"加锁前");[lock lock];NSLog(@"加锁后");if (value > 0) {NSLog(@"value——%d", value);block(value - 1);}[lock unlock];};block(10);});
}

请添加图片描述
输出结果并没有按代码表面的想法去走,而是只打印了一次value值
原因: 互斥锁在递归调用时会造成堵塞,并非死锁——这里的问题是后面的代码无法执行下去

  • 第一次加完锁之后还没出锁就进行递归调用
  • 第二次加锁就堵塞了线程(因为不会查询缓存)
    解决方案: 使用递归锁NSRecursiveLock替换NSLock

NSRecursiveLock

使用

NSRecursiveLock使用和NSLock类似,如下代码就能解决上个问题

- (void)test {NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];dispatch_async(dispatch_get_global_queue(0, 0), ^{static void (^block)(int);block = ^(int value) {[lock lock];if (value > 0) {NSLog(@"value——%d", value);block(value - 1);}[lock unlock];};block(10);});
}

NSRecursiveLock在YYKit中YYWebImageOperation.m中有用到

注意事项

递归锁在使用时需要注意死锁问题——前后代码相互等待便会产生死锁

NSCondition

NSCondition是一个条件锁,可能平时用的不多,但与信号量相似:线程1需要等到条件1满足才会往下走,否则就会堵塞等待,直至条件满足

同样的能在Swift源码中找到关于NSCondition部分

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)}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?
}

从上述精简后的代码可以得出以下几点:

  • NSCondition是对mutexcond的一种封装(cond就是用于访问和操作特定类型数据的指针)
  • wait操作会阻塞线程,使其进入休眠状态,直至超时
  • signal操作是唤醒一个正在休眠等待的线程
  • broadcast会唤醒所有正在等待的线程

NSConditionLock

顾名思义,就是NSCondition + Lock
那么和NSCondition的区别在于哪里呢?接下来看一下NSConditionLock源码

open class NSConditionLock : NSObject, NSLocking {internal var _cond = NSCondition()internal var _value: Intinternal 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?
}

从上述代码可以得出以下几点:

  • NSConditionLockNSCondition加线程数的封装
  • NSConditionLock可以设置锁条件,而NSCondition只是无脑的通知信号

os_unfair_lock

由于OSSpinLock自旋锁的bug,替代方案是内部封装了os_unfair_lock,而os_unfair_lock在加锁时会处于休眠状态,而不是自旋锁的忙等状态在这里插入图片描述

总结

  • OSSpinLock不再安全,底层用os_unfair_lock替代
  • atomic只能保证settergetter时线程安全,所以更多的使用nonatomic来修饰
  • 读写锁更多使用栅栏函数来实现
  • @synchronized在底层维护了一个哈希链表进行data的存储,使用recursive_mutex_t进行加锁
  • NSLock、NSRecursiveLock、NSConditionNSConditionLock底层都是对pthread_mutex的封装
  • NSConditionNSConditionLock是条件锁,当满足某一个条件时才能进行操作,和信号量dispatch_semaphore类似
  • 普通场景下涉及到线程安全,可以用NSLock
  • 循环调用时用NSRecursiveLock
  • 循环调用且有线程影响时,请注意死锁,如果有死锁问题请使用@synchronized

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/11140.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

单片机中的通用LED驱动

前言 项目中需要用到很多的LED灯&#xff0c;存在不同的闪烁方式&#xff0c;比如单闪&#xff0c;双闪&#xff0c;快闪&#xff0c;慢闪等等&#xff0c;我需要一个有如下特性的LED驱动 方便的增加不同闪烁模式可以切换闪烁模式增加LED数目不会有太多的改动方便移植&#x…

【C++杂货铺】模板(文末有彩蛋哟)

文章目录 一、泛型编程二、函数模板2.1 函数模板的原理2.2 函数模板的实例化2.3 模板参数的匹配原则 三、类模板四、非类型模板参数五、模板的特化5.1 函数模板特化5.2 类模板特化 六、模板分离编译七、模板总结好书推荐&#x1f381;彩蛋 一、泛型编程 &#x1f4d6;实现一个…

Spring Boot单元测试入门指南

Spring Boot单元测试入门指南 JUnit是一个成熟和广泛应用的Java单元测试框架&#xff0c;它提供了丰富的功能和灵活的扩展机制&#xff0c;可以帮助开发人员编写高质量的单元测试。通过JUnit&#xff0c;开发人员可以更加自信地进行重构、维护和改进代码&#xff0c;同时提高代…

volley 学习笔记1--发送请求

一、概览 Volley 具有以下优势&#xff1a; 自动网络请求调度。 多个并发网络连接。 透明磁盘和具有标准 HTTP 缓存一致性的内存响应缓存。 支持请求优先级。 取消请求 API。您可以取消单个请求&#xff0c;也可以设置要取消的请求的时间段或范围。 可轻松自定义&#xff…

linux下i2c调试神器i2c-tools安装及使用

i2c-tools介绍 在嵌入式linux开发中&#xff0c;有时候需要确认i2c硬件是否正常连接&#xff0c;设备是否正常工作&#xff0c;设备的地址是多少等等&#xff0c;这里我们就需要使用一个用于测试I2C总线的工具——i2c-tools。 i2c-tools是一个专门调试i2c的开源工具&#xff…

Kafka 入门到起飞系列 - 消费者组管理、位移管理

消费者组 - Consumer Group 上文我们已经讲过消费者组了&#xff0c;我们知道消费组的存在可以保证一个主题下一个分区的消息只会被组内一个消费者消费&#xff0c;从而避免了消息的重复消费 什么是消费组 - Consumer Group&#xff1f; 消费者组是Kafka 提供的可扩展且具有容…

事后多重比较方法

一、案例介绍 由单因素方差分析案例中&#xff0c;为研究郁金对低张性缺氧小鼠存活时间的影响&#xff0c;将36只小鼠随机生成A、B以及 C 三组&#xff0c;每组12个&#xff0c;雌雄各半&#xff0c;分别以10g/kg、20g/kg、40g/kg三种不同剂量的郁金灌胃&#xff0c;各组小鼠均…

从原理到实践,分析 Redisson 分布式锁的实现方案(二)

上篇讲解了如何用 Redis 实现分布式锁的方案&#xff0c;它提供了简单的原语来实现基于Redis的分布式锁。然而&#xff0c;Redis作为分布式锁的实现方式也存在一些缺点。本文将引入Redisson来实现分布式锁。 一、Redisson是什么 Redisson是一个基于Redis的分布式Java框架。它提…

信息安全:网络安全体系 与 网络安全模型.

信息安全&#xff1a;网络安全体系 与 网络安全模型. 网络安全保障是一项复杂的系统工程&#xff0c;是安全策略、多种技术、管理方法和人员安全素质的综合。一般而言&#xff0c;网络安全体系是网络安全保障系统的最高层概念抽象&#xff0c;是由各种网络安全单元按照一定的规…

抖音seo短视频矩阵系统源代码开发技术分享

抖音SEO短视频矩阵系统是一种通过优化技术&#xff0c;提高在抖音平台上视频的排名和曝光率的系统。以下是开发该系统的技术分享&#xff1a; 熟悉抖音平台的算法 抖音平台的算法是通过分析用户的兴趣爱好和行为习惯&#xff0c;对视频进行排序和推荐。因此&#xff0c;开发人员…

Visitor设计模式访问元素方法的问题

Visitor设计模式访问元素方法的问题 GPT给出的答案寻找灵感前置声明Element层次的实例Visitor interface的声明Element interface的声明Element实际类的声明及实现实现一个Visitor客户端代码 实战测试结果 针对C来说&#xff0c;若要实现Visitor设计模式&#xff0c;则会面临循…

SAP安装笔记

1、准备安装介质&#xff0c;SWPM10SP25&#xff0c;51050829_NW750_JavaExport、SAP_HANA_CLIENT、kernel放到/sapcd/NetWeaver目录下 ​​​​​​​ 进入SWPM10SP25执行./sapinst安装 2、待出现 “Open your browser and paste the following URL address to access the G…

上门家政系统开发|上门预约家政小程序定制系统

随着人们生活水平的提高&#xff0c;对于家政服务的需求也越来越高。上门家政小程序的开发为家政服务商家提供了一个全新的经营和服务渠道。本文将介绍上门家政小程序适合的商家以及其优势。   1. 家政公司   家政公司是最直接受益于上门家政小程序开发的商家。通过开发家政…

企业博客资讯如何高效运营起来?

运营一个高效的企业博客资讯需要综合考虑多个因素&#xff0c;包括内容策划、发布频率、优化推广、互动反馈等。下面将从这些方面介绍如何高效运营企业博客资讯。 如何高效运营企业博客资讯 内容策划 首先&#xff0c;需要制定一个明确的内容策略。确定博客的定位和目标受众…

【C语言】指针进阶(二)

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

【UE5 多人联机教程】04-加入游戏

效果 步骤 1. 新建一个控件蓝图&#xff0c;父类为“USC_Button_Standard” 控件蓝图命名为“UMG_Item_Room”&#xff0c;用于表示每一个搜索到的房间的界面 打开“UMG_Item_Room”&#xff0c;在图表中新建一个变量&#xff0c;命名为“Session” 变量类型为“蓝图会话结果…

MB5B在HDB上的性能调优

背景 MB5B是用于查询物料的收发以及现有库存。日常业务查询,通常会按照月份查看某片地区物料的库存以及收发状态。 调优思路 按照客户日常操作的习惯,得到日常操作的数据范围,选出数据量最为突出最有代表性的地区和物料;利用SE30分别运行不同数量级的数据,比如20个门店、…

利用sklearn 实现线性回归、非线性回归

代码&#xff1a; import pandas as pd import numpy as np import matplotlib import random from matplotlib import pyplot as plt from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import LinearRegression# 创建虚拟数据 x np.array(r…

DAY3,Qt(完成闹钟的实现,定时器事件处理函数的使用)

1.完成闹钟的实现&#xff0c;到点播报文本框的内容&#xff1b; ---alarm.h---头文件 #ifndef ALARM_H #define ALARM_H#include <QWidget> #include <QTimerEvent> //定时器处理函数类 #include <QTime> //时间类 #include <QPushButton> //按钮…

蓝牙技术|智能照明市场蓬勃发展,蓝牙技术助力市场发展

照明控制系统在商业和工业领域的应用广泛。例如&#xff0c;智能办公楼、商场、工厂等场所&#xff0c;可以通过照明控制系统实现节能和舒适性的提升。预计将从2023年的74亿美元增长到2032年的108亿美元&#xff0c;复合年增长率(CAGR)为4.3%。 随着LED照明技术在市场上的逐渐普…