算法通信协议定了一个大pb,然后往pb里面塞入各种特征。这些特征会去不同的redis表查询获取。redis查询不同表,解析返回数据,因为回调函数做成了并发异步,所以不是各个特征请求间不是串行序列化进行,所以会存在一个并发竞争问题。
对于自定义复杂对象非pld类型,protobuf (c ) 使用两种方法设置内嵌class对象的值,分别是set_allocated_ 和 mutable_。这个mutable_如果在非线程安全的环境被滥用,会有潜在的内存泄漏,这个问题比较隐蔽。
假设我们有一个例子:
代码语言:javascript复制message browser_user_feature
{
feature_user_download user_download = 1;
feature_query_word query_word = 2;
}
message UserFeature {
browser_user_feature user_feature = 1;
}
然后用户下载数据和query数据是通过不同的redis表获取。然后我们注册回调函数去解析, 这是伪代码
代码语言:javascript复制register_parse_func("key1", 解析的user_download的回调函数)
register_parse_func("key2", 解析的query_word的回调函数)
解析的user_download的回调函数(data, size) {
result.mutable_user_feature()->mutable_user_download()->ParseFromString(data, size);
}
解析的query_word的回调函数(data, size) {
result.mutable_user_feature()->mutable_query_word()->ParseFromString(data, size);
}
那么mutable这个方面内部会进行是否空指针判断,如果是空指针,进行对象内存分配。
代码语言:javascript复制 inline ::feature_process::browser_user_feature* UserFeature::mutable_browser_search_user_feature() {
if (browser_user_feature_ == nullptr) {
auto* p = CreateMaybeMessage<::feature_process::browser_user_feature>(GetArenaNoVirtual());
browser_user_feature_ = p;
}
// @@protoc_insertion_point(field_mutable:feature_process.UserFeature.browser_user_feature)
return browser_user_feature_;
}
使用内存分析工具:gperftools https://github.com/gperftools/gperftools,会看到大量的内存分配在这个函数CreateMaybeMessage。这里注意mutable_xxx不是线程安全函数,所以分配的时候,可能会出现分配两次或多次的问题,那么后面即使释放内存也只会释放一次。
那修改这个bug的方法是什么呢?我们要保证在一个不存在线程安全问题的地方先调用一下mutable方法,预分配下内存,后续用到的时候就不会创建新内存。