@synchronized(互斥锁) 原理

1.clang分析实现原理

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

分析:

  • 1.objc_sync_enter(_sync_obj);
  • 2._SYNC_EXIT 结构体
  • 3._sync_exit(_sync_obj);

objc_sync_enter 函数探索

int objc_sync_enter(id obj){int result = OBJC_SYNC_SUCCESS;if (obj) {// obj 存在SyncData* data = id2data(obj, ACQUIRE);ASSERT(data);data->mutex.lock();} else {// obj 不存在 什么事情也不做}return result;
}

分析:

  • 1.被锁对象obj 为空的时候都是什么事情也不会做。
  • 2.核心函数id2data (两个状态参数:ACQUIRERELEASE
  • 3.SyncData 的数据结构。

SyncData 数据结构

typedef struct alignas(CacheLineSize) SyncData {struct SyncData* nextData;DisguisedPtr<objc_object> object;int32_t threadCount;     // 使用此块的线程数recursive_mutex_t mutex; //递归锁
} SyncData;

分析:

  • 1.nextData 判断为单向链表,头插法。
  • 2.threadCount 多线程标记
  • 3.mutex 递归锁

id2data 函数实现分析

struct SyncList {SyncData *data;spinlock_t lock;constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { }
};
// 使用多个并行列表来减少不相关对象之间的争用
#define LOCK_FOR_OBJ(obj) sDataLists[obj].lock
#define LIST_FOR_OBJ(obj) sDataLists[obj].data
// 全局静态变量   哈希表
static StripedMap<SyncList> sDataLists;
// 哈希函数 确定一个下标
// 可能会发生冲突 (之前通过在哈希来解决)
// 这里 使用 拉链法
...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//支持线程局部存储// 检查每个线程的单条目快速缓存是否匹配对象bool fastCacheOccupied = NO;SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);// 第一部分if (data) { ... }#endif// 检查已经拥有的锁的每个线程缓存是否匹配对象SyncCache *cache = fetch_cache(NO);// 第二部分if (cache) { ... }//线程缓存没有找到任何东西//遍历正在使用的列表以查找匹配的对象//自旋锁防止多个线程创建多个//锁定相同的新对象//我们可以把节点保存在某个哈希表中,如果有的话 //有20多个不同的锁在工作中,但我们现在不这么做。//这里的锁 是为了保证在开辟内存空间时候的安全, 和外面的锁不一样哦, 此处是一个 spinlock_t 在上面有定义lockp->lock(); // 第三部分// 代码块{ ... }// 分配一个新的SyncData并添加到列表中.// XXX在持有全局锁的情况下分配内存是不好的做法,// 可能值得释放锁,重新分配,再搜索一次.// 但由于我们从不释放这些人我们不会经常陷入分配中.// 第四部分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(); //这里的锁 是为了保证在开辟内存空间时候的安全, 和外面的锁不一样哦,此处是一个 spinlock_t 在上面有定义// 第五部分if (result) { ... }return result;
}
  • 1: 创建一张全局的哈希表(static StripedMap sDataLists;), 使用拉链法来存储 SyncData;这里的全局表中存放不同对象,拉链拉的是通过hash计算得到的相同索引与相同对象的不同线程SyncData
  • 2: sDataLists, 中array 存储的是 SyncList (绑定的是objc,我们加锁的对象)
  • 3: objc_sync_enterobjc_sync_exit 调用对称,封装的是递归锁
  • 4: 两种存储 :TLS(线程局部存储) / cache(线程缓存)
  • 5: 第一次 syncData 头插法 链表 标记 threadCount = 1
  • 6: 判断是否同一个对象:如果是,lockCount++
  • 7: TLS找到 -> lock ++
  • 8: TLS 找不到 sync threadCount ++
  • 9: lock — threadCount—
    Synchronized : 可重入 递归 多线程
  • 1: TLS 保障 threadCount 多少条线程对这个锁对象加锁
  • 2: lock ++ 进来多少次

NSCondition和NSConditionLock

NSCondition
条件锁,顾名思义,就是满足某些条件才会开锁。NSCondition,可以确保线程仅在满足特定条件时才能获取锁。一旦获得了锁并执行了代码的关键部分,线程就可以放弃该锁并将关联条件设置为新的条件。条件本身是任意的:可以根据应用程序的需要定义它们。

NSCondition对象实际上作为一个锁和一个线程检查器:锁主要为了当检测条件时保护数据源,执行条件引发的任务;线程检查器主要是根据条件决定是否继续运行线程,即线程是否被阻塞。通俗的说,也就是条件成立,才会执行锁住的代码。条件不成立时,线程就会阻塞,直到另一个线程向条件对象发出信号解锁为止。

下面我们看一个例子:

- (void)conditionTest {for (int i = 0; i < 50; i++) {dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{[self addTickets];});dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{[self addTickets];});dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{[self minusTickets];});dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{[self minusTickets];});}
}- (void)addTickets {self.ticketCount += 1;NSLog(@"加一个现有ticketCount==%zd",self.ticketCount);
}- (void)minusTickets {while (self.ticketCount == 0) {NSLog(@"==没有ticketCount==");return;}self.ticketCount -= 1;NSLog(@"减一个剩下ticketCount==%zd",self.ticketCount);
}

运行程序,我们发现控制台的输出是有问题的

此时,我们就可以使用条件锁解决问题。我们只需要对程序作如下改动就可以正常执行:

- (void)addTickets {[self.condition lock];self.ticketCount = self.ticketCount + 1;NSLog(@"现有ticketCount==%zd",self.ticketCount);[self.condition unlock];[self.condition signal];
}- (void)minusTickets {[self.condition lock];while (self.ticketCount == 0) {NSLog(@"==没有ticketCount==");[self.condition wait];return;}self.ticketCount -= 1;NSLog(@"减一个,剩下ticketCount==%zd",self.ticketCount);[self.condition unlock];
}

需要使用swift源码进行查看:

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()}// 一般用于多线程同时访问、修改同一个数据源,保证在同一 时间内数据源只被访问、修改一次,// 其他线程的命令需要在lock 外等待,只到 unlock ,才可访问open func lock() {pthread_mutex_lock(mutex)}// 释放锁,与lock成对出现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?
}

可以看到,该对象还是对pthread_mutex的一层封装,NSCondition也是一种互斥锁。当我们需要等待某个条件的时候,也就是条件不满足的时候,就可以使用wait方法来阻塞线程,当条件满足了,使用signal方法发送信号唤醒线程。

NSConditionLockNSCondition又做了一层封装,自带条件探测,能够更简单灵活的使用。

我们使用swift查看一下:

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
}// 表示 xxx 期待获得锁,
// 如果没有其他线程获得锁(不需要判断内部的 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
}// 表示如果没有其他线程获得该锁,但是该锁内部的 condition不等于A条件,它依然不能获得锁,仍然等待。
// 如果内部的condition等于A条件,并且没有其他线程获得该锁,则执行任务,同时设置它获得该锁
// 其他任何线程都将等待它代码的完成,直至它解锁。
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)
}// 表示释放锁,同时把内部的condition设置为A条件
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
}// 表示如果被锁定(没获得 锁),并超过该时间则不再阻塞线程。
// 需要注意的是:返回的值是NO,它没有改变锁的状态,这个函 数的目的在于可以实现两种状态下的处理
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?

可以看出,触发的唤醒线程的条件是传入的condition取值,和我们创建锁的时候值要相同,我们可以在释放当前线程锁的时候重新设置其他线程传入的condition值,这样也就达到了唤醒其他线程的目的。如果创建锁的值和传入的值都不能匹配,则会进入阻塞状态。

下面我们来看个例子:

- (void)conditionLockTest {NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{[conditionLock lockWhenCondition:1];NSLog(@"线程1");[conditionLock unlockWithCondition:0];});dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{[conditionLock lockWhenCondition:2];NSLog(@"线程2");[conditionLock unlockWithCondition:1];});dispatch_async(dispatch_get_global_queue(0, 0), ^{[conditionLock lock];NSLog(@"线程3");[conditionLock unlock];});
}

执行流程首先搞明白传入的2的作用,这个值只是一个初始条件,
他的配套使用方法是lockWhenCondition通过判断条件是否相等来决定是否等待,并且在使用过程中这个条件可以更改,从而达到按照一定顺序执行任务的目的。如果不使用条件将不受条件控制。
总结
相同点:
都是互斥锁
通过条件变量来控制加锁、释放锁,从而达到阻塞线程、唤醒线程的目的
不同点:

NSCondition是基于对pthread_mutex的封装,而NSConditionLock是对NSCondition做了一层封装
NSCondition是需要手动让线程进入等待状态阻塞线程、释放信号唤醒线程,NSConditionLock则只需要外部传入一个值,就会依据这个值进行自动判断是阻塞线程还是唤醒线程

iOS 锁的底层原理相关推荐

  1. 【MySQL进阶】MySQL事务隔离与锁机制底层原理万字总结(建议收藏!!)

    [MySQL进阶]MySQL事务隔离与锁机制底层原理万字总结(建议收藏!!) 参考资料: 美团技术团队:Innodb中事务隔离级别和锁的关系 数据库的锁,到底锁的是什么? 阿里面试:说说一致性读实现原 ...

  2. java锁的底层原理

    知识整理 Synchronized 内置锁,JVM级别 使用 底层 锁升级过程.CAS操作的缺点[替换线程和copy mw] 优化 代码优化:同步代码块.减少锁粒度.读锁并发 JDK自带 偏置锁.轻量 ...

  3. iOS进阶之底层原理-锁、synchronized

    锁主要分为两种,自旋锁和互斥锁. 自旋锁 线程反复检查锁变量是否可用,处于忙等状态.一旦获取了自旋锁,线程会一直保持该锁,直至释放,会阻塞线程,但是避免了线程上下文的调度开销,适合短时间的场合. 互斥 ...

  4. iOS之深入解析“锁”的底层原理

    一.OSSpinLock(自旋锁) 自从 OSSpinLock 出现安全问题,在 iOS10 之后就被 Apple 废弃.自旋锁之所以不安全,是因为获取锁后,线程会一直处于忙等待,造成了任务的优先级反 ...

  5. iOS进阶之底层原理-线程与进程、gcd

    线程与进程 线程的定义 线程是进程的基本单位,一个进程的所有任务都在线程中执行 进程要想执行任务,必须的有线程,进程至少要有一条线程 程序启动默认会开启一条线程,也就是我们的主线程 进程的定义 进程是 ...

  6. iOS 进阶之底层原理一OC对象原理alloc做了什么

    人狠话不多,直接上干货.这是第一篇,之后还会持续更新,当作自己学习的笔记,也同时分享给大家,希望帮助更多人. 首先,我们来思考,下面这段代码的输出是否相同.答案很明显,p1.p2.p3是指向相同的对象 ...

  7. Synchronized锁升级底层原理

    思考问题 首先请您思考下面的问题: Synchronized锁同步机制性能不好嘛? 一个对象天生对应一个monitor锁吗? 为什么说synchronized是非公平锁? synchronized字节 ...

  8. iOS进阶之底层原理-block本质、block的签名、__block、如何避免循环引用

    面试的时候,经常会问到block,学完本篇文章,搞通底层block的实现,那么都不是问题了. block的源码是在libclosure中. 我们带着问题来解析源码: blcok的本质是什么 block ...

  9. iOS进阶之底层原理-weak实现原理

    基本上每一个面试都会问weak的实现原理,还有循环引用时候用到weak,今天我们就来研究下weak的实现原理到底是什么. weak入口 我们在这里打个断点,然后进入汇编调试. 这里就很明显看到了入口, ...

最新文章

  1. CentOS 安装Python3
  2. vnx vmax分盘过程
  3. qtdesigner怎么实现菜单栏跳转_3种公众号菜单栏设置类型,手把手教你做,不会的话那就再看一遍...
  4. 百度空间互踩_贝壳联手百度地图 整合新房信息找房更便捷
  5. 【BZOJ2631】tree (LCT)
  6. lampp mysql 等待响应时间很长_XAMPP 的 phpMyAdmin 就会有文件大小限制、上传超时等各种问题...
  7. 洛谷P1173:[NOI2016] 网格(tarjan、离散化)
  8. linux 环境 crontab+shell+sqlplus 调用oracle 存储过程实现数据同步
  9. ping/pong模式_PING的完整形式是什么?
  10. 超文本css样式换行
  11. ModuleNotFoundError: No module named 'tensorflow.python.saved_model.model_utils'
  12. Informix数据库安装配置
  13. java ide 的优劣_Java程序员的困惑 Java IDE到底怎么选
  14. cesium加载 gltf模型
  15. 《老罗Android开发视频教程》
  16. java多级继承_java代码继承------多层继承
  17. 网络基础虚拟化VRRP/MSTP冗余技术
  18. android的 root权限
  19. 二 关键词---关键词扩展(五)
  20. 定制化和极简主义风格的安卓,看你pick谁?

热门文章

  1. Matlab画x=a,y=b直线
  2. win7计算机文件夹折叠,win7系统折叠组窗口设置不折叠的操作方法
  3. 全国计算机软考程序员考试大纲
  4. Mysql - 百万级数据查询优化笔记 (PHP Script) ②
  5. 7-23 显示Pascal三角形 (30分)
  6. jqGrid系列:下载jqGrid
  7. Postman安装与卸载
  8. 精选100个Python实战项目案例,送给缺乏实战经验的你
  9. 用户表空间限额(Oracle User Space Quota )
  10. Linux驱动-字符设备驱动