1、atomic、nonatomic
1.1、set方法
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{
bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
bool mutableCopy = (shouldCopy == MUTABLE_COPY);
// 调用 reallySetProperty 方法
reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
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);
// 若设置为 copy 属性
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
// nonatomic 直接赋值
oldValue = *slot;
*slot = newValue;
} else {
// atomic 赋值操作需要加锁
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
- 如果是atomic属性,进行set前会进行加锁,set后解锁
1.2、get方法
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_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属性,进行get前会进行加锁
小结
-
atomic 是对属性的 setter 与 getter 进行加锁,单次读写能保证完整,但多线程下但并不能完全保证线程安全
-
单线程 中使用atomic执行 setter 和 getter 时线程安全(如setter过程中调用了getter,那么会等到setter完成后才会执行getter),但在 多线程 下又有getter又有setter时,get到的值不可控(线程1调用 getter,线程2调用 setter,线程3调用 setter 这3个线程并行同时开始)
2、锁的类型
2.1、自旋锁
- 是一种
盲等,线程不会休眠,相当于有do while循环卡在里边 - 因为线程不会进行休眠,省去了线程间的调度,因此 执行效率高,但 占用大量资源
2.1.1、OSSpinLock(苹果已废弃)
- 在线程有不同优先级的情况下会出现
优先级反转的问题
2.1.2、atomic
2.1.3、dispatch_semaphore_t
2.2、互斥锁
- 是一种
闲等,资源被锁时线程会休眠,CPU会去调度其他线程工作
2.2.1、os_unfair_lock
| 方法 | 作用 |
|---|---|
| OS_UNFAIR_LOCK_INIT | 初始化锁 |
| os_unfair_lock_lock(&锁) | 加锁 |
| os_unfair_lock_unlock(&锁) | 解锁 |
| os_unfair_lock_trylock(&锁) | 尝试加锁,成功返回true、失败返回false |
| 因为会触发崩溃因此不常用的: | |
| os_unfair_lock_assert_owner(&锁) | 若当前线程未持有指定锁或者已解锁,则崩溃 |
| os_unfair_lock_assert_not_owner(&锁) | 若当前线程持有指定锁则崩溃 |
- 如果在使用的位置直接声明的锁,还是会有线程安全的情况
2.2.2、NSLock
不能递归调用(未对加锁资源解锁时又进行加锁),否则死锁
| 方法 | 作用 |
|---|---|
| -(void)lock | 加锁 |
| -(void)unlock | 解锁 |
| -(BOOL)trylock | 尝试加锁,成功返回YES、失败返回NO |
| -(BOOL)lockBeforeDate:(NSDate *)limit | 在指定时间点之前获取锁,能获取返回YES、不能返回NO |
| @property(nullable,copy)NSString *name | 锁名称 |
2.2.3、NSRecursiveLock(递归锁)
- 与 NSLock 方法相同,区别在于可用于递归中
必须在同一线程下,比如将上例的 dispatch_async 置于for循环中就会产生死锁,因为会开辟多条线程
2.2.4、NSCondition
- 虚假唤醒:如果有多个线程在条件变量发出信号时等待他,系统可能会将所有线程唤醒(signal当做broadcast),从而打破了信号与唤醒间预期的1:1关系
| 方法 | 作用 |
|---|---|
| -(void)lock | 加锁 |
| -(void)unlock | 解锁 |
| -(void)wait | 阻塞当前线程,使线程进入休眠,等待唤醒信号,调用前必须已加锁 |
| -(void)waitUntilDate | 阻塞当前线程,使线程进入休眠,等待唤醒信号或超时,调用前必须已加锁 |
| -(void)signal | 唤醒一个正在休眠的线程,若要唤醒多个则需调用多次,调用前必须已加锁 |
| -(void)broadcast | 唤醒所有在等待的线程,调用前必须已加锁 |
| @property(nullable,copy)NSString *name | 锁名称 |
2.2.5、NSConditionLock
- 是基于 NSCondition 的封装,自带条件探测,可以实现
控制线程调用顺序的需求,与信号量相似
2.3、读写锁
多读单写、读与写互斥
// 通过GCD实现读写锁
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic,strong) dispatch_queue_t queue;
@property (nonatomic,strong) NSMutableDictionary *dic;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.queue = dispatch_queue_create("LZ", DISPATCH_QUEUE_CONCURRENT);
self.dic = [NSMutableDictionary new];
// 多读通过for循环开辟线程实现
for (int i = 0; i<10; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self readMethod];
});
}
}
- (NSString *)readMethod {
__block NSString *ret;
// 与 写操作 在同一队列中;使用sync是为了保证return不会先被执行
dispatch_sync(self.queue, ^{
ret = self.dic[@"name"];
});
NSLog(@"%@",ret);
return ret;
}
- (void)writeMethod:(NSDictionary *)dic {
// 通过栅栏函数,和 读操作 在同一队列中,来保证读写互斥
dispatch_barrier_async(self.queue, ^{
[self.dic setDictionary:dic];
});
}
- 多读单写:async开辟多线程进行读操作
- 读写互斥:通过将 读操作 和 写操作 放在在同一队列中,且 写操作 使用栅栏函数,来实现读写互斥
小结
-
自旋锁是盲等,互斥锁是闲等
-
@synchronized 在模拟器下性能不及其他锁,但在真机下与其他锁效率相差不大,又因它的使用简单,因此被使用的最广泛,也单独作为一篇
-
遵守
NSLocking协议的都有 lock 与 unlock 方法@protocol NSLocking - (void)lock; - (void)unlock; @end -
普通锁与递归锁的区别:
- 锁:同一时刻只能被一条线程锁拥有
- 递归锁:同一时刻能被多条线程锁拥有
-
很多锁其实都是基于
pthread_mutex的封装- 构造方法
init()就是调用了pthread的pthread_mutex_init(mutex, nil)方法 - 析构方法
deinit就是调用了pthread的pthread_mutex_destroy(mutex)方法 - 加锁方法
lock()就是调用了pthread的pthread_mutex_lock(mutex)方法 - 解锁方法
unlock()就是调用了pthread的pthread_mutex_unlock(mutex)方法 - 在
pthread_mutex中可以通过pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))来设置锁为递归锁,像NSLock就并没有设置,所以在使用的时候需要注意 NSLock 并不能在递归函数中使用
- 构造方法
3、pthread_mutex
既然很多锁都是基于pthread的封装,我们下边列出pthread的方法作为资料以供参考:
| 方法 | 作用 |
|---|---|
| pthread_mutex_init(pthread_mutex_t mutex, const pthread_mutexattr_t attr) | 初始化锁,pthread_mutexattr_t可用来设置锁的类型(是否为递归锁) |
| pthread_mutex_lock(pthread_mutex_t mutex) | 加锁 |
| pthread_mutex_trylock(*pthread_mutex_t *mutex) | 加锁,但是上面方法不一样的是当锁已经在使用的时候,返回为EBUSY,而不是挂起等待,成功返回0.失败返回错误信息 |
| pthread_mutex_unlock(pthread_mutex_t *mutex) | 解锁 |
| pthread_mutex_destroy(pthread_mutex_t* mutex) | 使用完锁之后释放锁 |
| pthread_mutexattr_setpshared() | 设置互斥锁的范围 |
| pthread_mutexattr_getpshared() | 获取互斥锁的范围 |