锁的分类
OC中锁分为自旋锁和互斥锁
1. 自旋锁
线程反复检查锁变量是否可用,由于线程在这一过程中保持执行,因此是一种忙等待
,线程会⼀直保持该锁,直⾄显式释放⾃旋锁。 ⾃旋锁避免了进程上下⽂的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。
在多CPU
的环境中,对持有锁较短的程序来说,使用自旋锁代替一般的互斥锁往往能够提高程序的性能。
2.互斥锁
是⼀种⽤于多线程编程中,防⽌两条线程同时对同⼀公共资源(⽐
如全局变量)进⾏读写的机制。该⽬的通过将代码切⽚成⼀个⼀个的临界区⽽达成
- 互斥和同步的理解
互斥:两条线程处理,同一时间只有一天线程可以运行
同步:了有互斥的意思外,同时还有一定的顺序要求,即按照一定的顺序执行。 - 递归锁
同一线程可以加锁多次,而不会引起死锁NSRecursiveLock、@synchronized、pthread_mutex(recursive)
互斥锁:@synchronized,NSLock,NSConditionLock,NSCondition,NSRecursiveLock,dispatch_semaphore_t
自旋锁和互斥锁的特点
- 自旋锁会
忙等
:在访问被锁资源时,调用者线程不会休眠,而是不停的循环等待,知道被锁资源被解锁。由于这个机制,在短时间就可以获取锁的情况下,自旋锁效率是优于互斥锁
,由于不会休眠
,就不会有CPU时间片轮转的耗时操作
- 互斥锁会休眠:在访问被锁资源时,调用者线程会休眠,此时
CPU
会轮转到其他线程执行任务,知道被锁资源解锁,才会唤醒休眠线程
锁的性能
TLS线程
线程局部存储(Thread Local Storage TLS):是操作系统为线程单独提供的私有空间,通常只有有限的容量。
@synchronized原理
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *p1 = [LGPerson alloc];
@synchronized (p1) {
};
}
}
我们来clang
一下
在这里我们发现了两个函数
objc_sync_enter
和objc_sync_exit
,接下来我们来打断点验证下这俩函数objc_sync_enter
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;
}
由objc_sync_enter
源码可知,@ synchronized
所传的对象不能为nil
,传nil
相当于什么都没干
objc_sync_exit
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;
}
先查看下SyncData
结构体
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;
-
struct SyncData* nextData
包含了一个相同的数据结构,由此可知,这是个单向链表 -
object
使用DisguisedPtr
进行包装 -
threadCount
线程数量,表示有多少个线程对该对象进行加锁 -
recursive_mutex_t
递归锁
由此可知synchronized
支持递归锁
,且支持多线程
SyncList
分析
SyncData
存出来一个全局的hash表中
,见下方代码
static StripedMap<SyncList> sDataLists;
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
}
先来看下StripedMap
结构体,在模拟器状态下有64
个SyncList
,真机状态8个
,接下来看下SyncList
struct SyncList {
SyncData *data;
spinlock_t lock;
constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { }
};
SyncData
是一个单向链表结构,从而形成了一个拉链结构
对比objc_sync_enter 和objc_sync_exit
两个源码,逻辑基本相同,最终都来到这句源码SyncData* data = id2data(obj, ACQUIRE);
,当加锁的时候传ACQUIRE
,解锁的时候传RELEASE
,进入id2data
方法
@synchronized不同情况跟踪
相同object单线程递归加锁
-
第一次进入data为
NULL
image.png -
cache也为
NULL
image.png -
该
for循环也未进入
image.png -
最终走到这里新创建一个该对象的
SyncData
,这里才用的是链表头插法算法
image.png 最后就是进行存储
-
第二次进入data是有值的
image.png -
进入
if判断
并且data->object == object
,并获取当前线程
该对象的锁的次数
并将锁的次数+1
并存储
image.png
-
第三次与第二次的步骤相同
image.png -
由于是在同一线程,当第三次结束时,将会进入
switch
的RELEASE
判断中,这里进行解锁的是第三次调用@ synchronized
,当lockcount == 0
的时候,证明当前线程中该对象已经全部解锁,所以threadCount -1
image.png
不同object单线程递归加锁
第一次进入的时候入data为
NULL
,cache
也为NULL,最终进行syncData
的创建-
第二次进入
data
存在,且在lldb
中打印sDataLists
,发现哈希下标第三个的data
,与获取出来的data
一致,说明上一次创建的syncData
已经存入
image.png -
但是
data->object 与 object
是不一致的,第一次进入的对象为p1
,第二次为p2
,并且cache
为NULL
image.png -
由于
p2
也是第一次进入,所以这里也要进行创建
image.png -
因为这里的
fastCacheOccupied
变量,在上面的if(data)判断里面已经改变成了YES,所以这里存入到了cache中
image.png -
第三次进入的是
p1
,data是存在的,且lldb打印得知p2
已经存入sDataLists
中
image.png -
接下来进入这里进行
lock + 1
操作
image.png
多线程加锁object的变化
-
第一次进入data还是为
NULL
,接下来进行创建并存储
image.png -
第二次进入的时候,发现这里进入的
object
为p1
是因为这里是多线程异步操作,所以p1
先进来解锁
image.png -
这里的
lockcount变为了0
image.png -
接下来
object
进入发现是p1
,这个是我们在里面嵌套的那个p1
,且这里的data为NULL
,是因为tls_get_direct
取得是当前线程第一次存入的syncData
,而这个p1所在的线程是第一次调用
,所以这里为NULL
image.png -
最终进入这个
for循环中
,这里循环遍历的是全局的哈希表中的syncList
image.png -
这里对该对象的
threadcount + 1
,
image.png 最终进行存储
【总结】
当在同一线程中:从当前线程的tls中取出第一次存储的
syncData
,如果存在进行判断,当前的object
和data—>object
是否一致,一致则进行lockCount + 1
,不一致则去cache
中查找,如果能查找出lockCount + 1
,否则,进行创建当在不同线程中:前面步骤与
当在同一线程中
一致,接下来去遍历sDataLists里面的syncLists
,如果能查找出当前的这个对象,则对lockcount 和 threadcount 都加1
,否则进行创建