feature: Weex APM

This commit is contained in:
FantasticLBP
2025-12-30 21:07:15 +08:00
parent 1142064d28
commit 7ac7513900
158 changed files with 9880 additions and 280 deletions

View File

@@ -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、NSOperationAFNetworking、SDWebImage ,可以方便对 Operation 的状态管理和依赖管理
- NSThread主要用于实现常驻线程
- NSOperation Finished 之后如何移除KVO
- NSOperation Finished 之后如何移除KVO
- 锁操作的返回值检查不是 “可选的”,而是 “必须的”,区别仅在于「失败后是崩溃、打日志还是抛异常」,需根据组件的核心程度选择。
- 系统级开源库(如 libdispatch严格检查返回值非预期失败直接崩溃保证系统稳定性
- 第三方开源库AFNetworking/SDWebImage调试阶段断言 + Release 阶段日志(平衡调试和线上稳定性);
- 业务组件(如你的字典):推荐「断言 + 日志」模式 ——Debug 暴露问题Release 不崩溃且留痕;