mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 20:00:40 +00:00
feature: Weex APM
This commit is contained in:
@@ -1079,6 +1079,169 @@ QA:互斥递归锁,可以在不同线程中加锁吗?
|
||||
|
||||
|
||||
|
||||
#### 使用注意事项
|
||||
##### 加解锁必须成对
|
||||
加解锁必须成对出现,否则容易出现多线程性能问题。提供一种思路,利用 `@try-finally` 来保证加解锁必须成对存在,这个写法也是 Weex 官方的实现。
|
||||
Weex 中的 WXThreadSafeMutableDictionary 提供了一个线程安全的字典,其本质是通过加 pthread_muext_t 锁来维护内部的一个字典的。
|
||||
比如下面的代码
|
||||
|
||||
初始化锁相关的配置
|
||||
```Objective-C
|
||||
@interface WXThreadSafeMutableDictionary ()
|
||||
{
|
||||
NSMutableDictionary* _dict;
|
||||
pthread_mutex_t _safeThreadDictionaryMutex;
|
||||
pthread_mutexattr_t _safeThreadDictionaryMutexAttr;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation WXThreadSafeMutableDictionary
|
||||
|
||||
- (instancetype)initCommon
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
pthread_mutexattr_init(&(_safeThreadDictionaryMutexAttr));
|
||||
pthread_mutexattr_settype(&(_safeThreadDictionaryMutexAttr), PTHREAD_MUTEX_RECURSIVE); // must use recursive lock
|
||||
pthread_mutex_init(&(_safeThreadDictionaryMutex), &(_safeThreadDictionaryMutexAttr));
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [self initCommon];
|
||||
if (self) {
|
||||
_dict = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
```
|
||||
在字典操作的地方使用锁
|
||||
```Objective-C
|
||||
- (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey
|
||||
{
|
||||
id originalObject = nil; // make sure that object is not released in lock
|
||||
@try {
|
||||
pthread_mutex_lock(&_safeThreadDictionaryMutex);
|
||||
originalObject = [_dict objectForKey:aKey];
|
||||
[_dict setObject:anObject forKey:aKey];
|
||||
}
|
||||
@finally {
|
||||
pthread_mutex_unlock(&_safeThreadDictionaryMutex);
|
||||
}
|
||||
originalObject = nil;
|
||||
}
|
||||
```
|
||||
这么写的价值:**解锁逻辑「绝对执行」,彻底避免死锁**
|
||||
这是 `@try-finally` 最核心的价值 ——无论 try 块内发生什么(正常执行、提前 return、抛异常),finally 块的解锁逻辑一定会执行
|
||||
|
||||
对比无 try-finally 的写法
|
||||
```Objective-C
|
||||
// Bad: 若setObject抛异常,unlock不会执行→死锁
|
||||
pthread_mutex_lock(&_mutex);
|
||||
[_dict setObject:anObject forKey:aKey];
|
||||
pthread_mutex_unlock(&_mutex);
|
||||
```
|
||||
问题:`[_dict setObject:anObject forKey:aKey]` 可能抛异常(比如 aKey = nil 时会触发 NSInvalidArgumentException),若没有 finally,锁会被永久持有→其他线程调用 lock 时死锁,整个字典无法再操作。
|
||||
|
||||
设计优点:
|
||||
- `@try-finallly`:即使 try 内逻辑出错,finally 也会执行 pthread_mutex_unlock,保证锁最终释放,这是**线程安全的「兜底保障」**
|
||||
- 注意,不是 `try...catch...finally`: 如果加了 catch 逻辑,则字典的 key 为 nil 产生的崩溃也会被捕获掉,这属于不符合预期的行为。因为 key 为 nil 产生的原因太多了,可能是业务代码异常,也可能是数据异常,也可能是逻辑错误,如果一刀切直接用 `try...catch...finally` 捕获了异常,但是没有配置异常的收集、上报、处理逻辑,属于边界不清晰,本质是为了解决加解锁不匹配而可能带来的线程安全问题,却"多管闲事",把字典 key 为 nil 本该向上跑的异常而卡住了(这个问题不再赘述,是一个经典的策略问题,端上的异常发生时,安全气垫的“做与不做”问题)
|
||||
|
||||
##### `pthread_mutex_t` 的加解锁函数返回值处理
|
||||
「加解锁函数都有返回值,需要对返回值进行判断和处理」这个是意识也业务场景问题,先告诉你有返回值,看你的场景需要严格处理还是松散处理。类似 JS 的 `use strict`
|
||||
iOS 系统开源组件(如 libdispatch/GCD)、Apple 官方开源代码,以及知名第三方开源库(AFNetworking、SDWebImage 等)都会严格检查 pthread 锁相关函数的返回值—— 因为系统 / 核心库需要保证鲁棒性,避免锁操作失败导致的死锁、崩溃或线程安全漏洞
|
||||
|
||||
1. GCD 的处理(libdispatch)
|
||||
核心文件:dispatch/src/queue.c(队列的线程安全实现)
|
||||
```c
|
||||
// libdispatch 中 pthread_mutex_lock 返回值检查的典型写法
|
||||
static inline void _dispatch_mutex_lock(pthread_mutex_t *m) {
|
||||
int ret = pthread_mutex_lock(m);
|
||||
// 严格检查返回值,仅允许「成功(0)」或「递归锁重复加锁(EDEADLK)」(针对递归锁场景)
|
||||
if (ret != 0 && ret != EDEADLK) {
|
||||
// 系统级库会触发 crash 并打印错误,避免静默失败
|
||||
dispatch_fatal("pthread_mutex_lock failed: %d", ret);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void _dispatch_mutex_unlock(pthread_mutex_t *m) {
|
||||
int ret = pthread_mutex_unlock(m);
|
||||
if (ret != 0) {
|
||||
dispatch_fatal("pthread_mutex_unlock failed: %d", ret);
|
||||
}
|
||||
}
|
||||
```
|
||||
说明:
|
||||
- 加锁
|
||||
- 对 pthread_mutex_lock,除了「成功(0)」,仅允许递归锁的「重复加锁(EDEADLK)」(递归锁场景下 EDEADLK 是预期行为);
|
||||
- 非预期返回值直接触发 dispatch_fatal(系统级崩溃),避免锁异常导致的隐性问题;
|
||||
- 解锁操作(pthread_mutex_unlock)仅接受「成功(0)」,失败则崩溃
|
||||
|
||||
思考:为什么 GCD 要这么做?
|
||||
系统库是「基础设施」,锁操作失败(如 EINVAL/ENOMEM)意味着系统资源耗尽或参数错误,属于「致命错误」—— 与其静默运行导致更严重的线程安全问题,不如直接崩溃并暴露问题。
|
||||
|
||||
2. AFNetworking(网络库,线程安全的缓存 / 队列实现)
|
||||
AFNetworking 的 AFURLSessionManager.m 中,对锁操作的返回值检查:
|
||||
```Objective-C
|
||||
// AFNetworking 中锁操作的返回值检查
|
||||
- (void)lock {
|
||||
int lockResult = pthread_mutex_lock(&_lock);
|
||||
NSAssert(lockResult == 0, @"Failed to lock mutex with error: %d", lockResult);
|
||||
}
|
||||
|
||||
- (void)unlock {
|
||||
int unlockResult = pthread_mutex_unlock(&_lock);
|
||||
NSAssert(unlockResult == 0, @"Failed to unlock mutex with error: %d", unlockResult);
|
||||
}
|
||||
```
|
||||
说明:
|
||||
- 用 NSAssert 检查返回值(Debug 模式下断言失败会崩溃,Release 模式下跳过);
|
||||
- 兼顾「调试阶段暴露问题」和「Release 阶段不影响运行」;
|
||||
- 符合第三方库的「友好调试 + 线上稳定性」平衡原则。
|
||||
|
||||
|
||||
3. CocoaLumberjack(日志库,线程安全的日志队列)
|
||||
CocoaLumberjack 的 DDLog.m 中,锁返回值检查的严谨写法:
|
||||
```Objective-C
|
||||
- (void)initLock {
|
||||
pthread_mutexattr_t attr;
|
||||
int ret = pthread_mutexattr_init(&attr);
|
||||
if (ret != 0) {
|
||||
DDLogError(@"pthread_mutexattr_init failed: %d", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
if (ret != 0) {
|
||||
DDLogError(@"pthread_mutexattr_settype failed: %d", ret);
|
||||
pthread_mutexattr_destroy(&attr);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = pthread_mutex_init(&_lock, &attr);
|
||||
if (ret != 0) {
|
||||
DDLogError(@"pthread_mutex_init failed: %d", ret);
|
||||
pthread_mutexattr_destroy(&attr);
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutexattr_destroy(&attr);
|
||||
}
|
||||
```
|
||||
说明:
|
||||
- 「初始化→设置属性→创建锁」全链路检查返回值
|
||||
- 失败时「回滚操作」(如销毁已初始化的 attr),避免资源泄漏
|
||||
- 打错误日志,便于问题排查
|
||||
|
||||
总结:
|
||||
核心原则:锁操作的返回值检查不是 “可选的”,而是 “必须的”,区别仅在于「失败后是崩溃、打日志还是抛异常」,需根据组件的核心程度选择。
|
||||
- 系统级开源库(如 libdispatch):严格检查返回值,非预期失败直接崩溃(保证系统稳定性);
|
||||
- 第三方开源库(AFNetworking/SDWebImage):调试阶段断言 + Release 阶段日志(平衡调试和线上稳定性);
|
||||
- 业务组件(如你的字典):推荐「断言 + 日志」模式 ——Debug 暴露问题,Release 不崩溃且留痕;
|
||||
|
||||
#### 互斥锁的条件变量 pthread_cond_t
|
||||
|
||||
多线程环境下,很多时候没办法确保先有数据再消费,比如生产者-消费者问题,这时候就有互斥锁的另一个 API 了,即条件变量`pthread_cond_t`
|
||||
@@ -2543,4 +2706,8 @@ APIClient.fetchData(...).then().onFailure();
|
||||
- GCD:简单的任务处理,以及多读单写、读写锁、dispatch_group_async
|
||||
- NSOperationQueue、NSOperation:AFNetworking、SDWebImage ,可以方便对 Operation 的状态管理和依赖管理
|
||||
- NSThread,主要用于实现常驻线程
|
||||
- NSOperation Finished 之后如何移除?KVO
|
||||
- NSOperation Finished 之后如何移除?KVO
|
||||
- 锁操作的返回值检查不是 “可选的”,而是 “必须的”,区别仅在于「失败后是崩溃、打日志还是抛异常」,需根据组件的核心程度选择。
|
||||
- 系统级开源库(如 libdispatch):严格检查返回值,非预期失败直接崩溃(保证系统稳定性);
|
||||
- 第三方开源库(AFNetworking/SDWebImage):调试阶段断言 + Release 阶段日志(平衡调试和线上稳定性);
|
||||
- 业务组件(如你的字典):推荐「断言 + 日志」模式 ——Debug 暴露问题,Release 不崩溃且留痕;
|
||||
Reference in New Issue
Block a user