1.概念
自旋锁:它是为实现保护共享资源而提出的一种锁机制。
自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。
无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。
调度机制上略有不同:
互斥锁:如果资源已经被占用,资源申请者只能进入睡眠状态。
自旋锁:不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。
可能存在两个问题:
试图递归地获得自旋锁必然会引起死锁:递归程序的持有实例在第二个实例循环,以试图获得相同自旋锁时,不会释放此自旋锁。在递归程序中使用自旋锁应遵守下列策略:递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。此外如果一个进程已经将资源锁定,那么,即使其它申请这个资源的进程不停地疯狂“自旋”,也无法获得资源,从而进入死循环。
过多占用cpu资源。如果不加限制,由于申请者一直在循环等待,因此自旋锁在锁定的时候,如果不成功,不会睡眠,会持续的尝试,单cpu的时候自旋锁会让其它process动不了. 因此,一般自旋锁实现会有一个参数限定最多持续尝试次数. 超出后, 自旋锁放弃当前time slice. 等下一次机会。
2.atomic原理
//属性
@property (atomic ,strong) NSString *name;
//对name赋值
self.name = @"iOS";
- 这里会调用setter方法,通过汇编,添加符号断点-[ViewController setName:],汇编我们可以看到调用了objc_setProperty_atomic方法:
- 既然调用的是objc_setProperty_atomic方法,那么就可以去objc源码去看了:
//原子性操作:
void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
//其实这里之前就已经看过了,setter底层都会调用reallySetProperty,工厂模式嘛
reallySetProperty(self, _cmd, newValue, offset, true, false, false);
}
//非原子性
void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
reallySetProperty(self, _cmd, newValue, offset, false, false, false);
}
//上面都会调用reallySetProperty,只有第五个参数传递的值不一致!!!!看下面的reallySetProperty方法实现,第五个参数就是 bool atomic
/**
self:隐含参数,对象消息接收者
_cmd:隐含参数,SEL方法编号
newValue:需要赋值的传入
offset:属性所在指针的偏移量
atomic:是否是原子操作
copy:是否是浅拷贝
mutableCopy:是否是深拷贝
*/
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool , 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 {
//原子性走这里
/**
StripedMap<spinlock_t> PropertyLocks;
using spinlock_t = mutex_tt<LOCKDEBUG>;
template <bool Debug>
class mutex_tt : nocopy_t {
os_unfair_lock mLock;
public:
constexpr mutex_tt() : mLock(OS_UNFAIR_LOCK_INIT) {
lockdebug_remember_mutex(this);
}
constexpr mutex_tt(const fork_unsafe_lock_t unsafe) : mLock(OS_UNFAIR_LOCK_INIT) { }
void lock() {
lockdebug_mutex_lock(this);
os_unfair_lock_lock_with_options_inline(&mLock, OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION);
}
void unlock() {
lockdebug_mutex_unlock(this);
os_unfair_lock_unlock_inline(&mLock);
}
......
}
*/
//先获取了spinlock_t锁
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();//加锁
//新旧值替换
oldValue = *slot;
*slot = newValue;
//解锁
slotlock.unlock();
}
//释放旧值
objc_release(oldValue);
}
- 看源码可以知道,非原子性时reallySetProperty传入false,原子判断时,会直接记录旧值,替换为新值,释放旧值;如果是原子性,传入的值是true,就会先去获得一把锁,锁住值替换代码,完成后解锁,释放旧值!!!
- 上面是setter方法,那么再去看看getter方法:
//atomic原子性传入的bool值
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
//获取加盐哈希表对应的值
id *slot = (id*) ((char*)self + offset);
//传入的值为false,表示不是原子性的,直接返回获取到的值
if (!atomic) return *slot;
//这里表示传入的atomic值为true,与setter一样,
//获取一把锁,加锁,获取到值,解锁,最后返回获取的值
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
//为了提高性能,我们(安全地)在自旋锁之外自动释放。
return objc_autoreleaseReturnValue(value);
}
- 总结一下:atomic本质上是在底层给setter/getter添加了一把锁,来保证线程安全!!!那么就意味着,代码在进入getter/setter时,线程是安全的,但是出了getter/setter时,多线程安全就只能靠程序员自己保证了!!!
- 举个栗子:
//atomic修饰
@property (atomic, strong) NSArray *atomicArr;
- (void)wm_test_atomic {
//异步任务 A
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 100000; i ++) {
if (i % 2 == 0) {
self.atomicArr = @[@"I", @"Love", @"VN"];
} else {
self.atomicArr = @[@"I can do five in a row"];
}
NSLog(@"异步任务 A: %@\n", self.atomicArr);
}
});
//异步任务 B
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 100000; i ++) {
NSString* str = [self.atomicArr objectAtIndex:1];
NSLog(@"异步任务 B ==== %@",str);
}
});
}
//这里运行的话会出现数组越界的crash
//atomic只是保证了setter/getter的安全,外部多线程操作,只有我们自己保证安全
- 再举个栗子:
//atomic修饰
@property (atomic ,assign) int num;
- (void)wm_test_atomic {
//异步任务 A
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 20000; i ++) {
self.num = self.num+1;
NSLog(@"异步任务 A:%@------%d",NSThread.currentThread,self.num);
}
});
//异步任务 B
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 20000; i ++) {
self.num = self.num+1;
NSLog(@"异步任务 B:%@------%d",NSThread.currentThread,self.num);
}
});
}
//打印结果?
- 这里打印结果只到39992,看代码,结果应该是40000,所以,atomic只能保证setter/setter安全!!!我们一定要自己处理多线程安全!
- atomic会消耗更多的资源,性能很低,比nonatomic慢将近20倍,使用的时候,还是建议使用nonatomic!