常见问题

使用 llbc 时遇到的高频问题汇总。每条简答后附交叉链接,方便深入阅读。


Q1:LLBC_Startup() 和 LLBC_Cleanup() 为什么必须成对调用?

LLBC_Startup() 按顺序初始化 common → core → comm → testcase → app 五个子模块(每个子模块有独立的 __LLBC_<Module>Startup() 入口);LLBC_Cleanup() 按相反顺序清理它们。未初始化就调用公共 API 会导致未定义行为(崩溃或断言失败),未清理则会泄漏全局资源(线程、定时器队列、日志后台线程等)。

推荐在 main() 入口处直接配对:

#include <llbc.h>
using namespace llbc;

int main()
{
    LLBC_Startup();

    // ... 业务逻辑 ...

    LLBC_Cleanup();
    return 0;
}

unit_test 的惯例是通过 gtest Environment 保证配对(见 tests/unit_test/UnitTest.cpp):

class TestEnv_LLBCIniter : public ::testing::Environment
{
public:
    void SetUp() override   { LLBC_Startup();  }
    void TearDown() override { LLBC_Cleanup(); }
};

Q2:克隆仓库后封装层(pyllbc/lullbc)或 unit_test 编译报找不到文件,为什么?

仓库含三个 git 子模块,普通 git clone 不会自动拉取:

子模块路径 用途
wrap/pyllbc/cpython Python 封装编译依赖
wrap/lullbc/lua Lua 封装编译依赖
tests/3rdparty/googletest unit_test 的 gtest/gmock

子模块目录存在但为空时,封装层会静默产出损坏的制品,CMake 的 unit_test 目标则会在链接 gtest 时失败。

正确做法:克隆时加 --recurse-submodules,或在已有克隆中补全:

# 方式 1:克隆时一次性拉取
git clone --recurse-submodules https://github.com/lailongwei/llbc.git

# 方式 2:已克隆后补全
git submodule update --init --recursive

Q3:日志配置文件里写了 logLevel 但不生效,怎么回事?

logLevel 已在 v1.1.1 废弃,框架不再读取该字段。请改用各 appender 级别的独立配置项:

废弃字段 替代字段 含义
logLevel consoleLogLevel 控制台输出的最低级别
logLevel fileLogLevel 文件输出的最低级别

同时注意:v1.1.1 对调了 Debug 与 Trace 的顺序,Trace 现在是最低级别(比 Debug 更详细)。

# 正确写法(ini 格式示例)
[root]
consoleLogLevel = Debug
fileLogLevel    = Trace

若日志文件或控制台没有输出,先确认 consoleLogLevel / fileLogLevel 已设置,且级别不高于实际打印调用的级别(Trace < Debug < Info < Warn < Error < Fatal)。


Q4:macOS 上网络性能很差,是 bug 吗?

不是 bug,是已知限制。llbc 的平台支持分两档:

macOS 上网络层使用 SelectPoller,性能显著低于 Linux 的 epoll 和 Windows 的 IOCP,在高连接数或高吞吐场景下会成为瓶颈。macOS 仅适合本地开发和功能调试,生产环境请部署到 Linux。


Q5:LLBC_ObjPool::Acquire<MyObj>() 编译报 static_assert 失败,但 MyObj 继承了 LLBC_Object,这是为什么?

LLBC_ObjPool 明确禁止管理 LLBC_Object 派生类(ObjPool.h 中有编译期断言):

// ObjPool.h 内部(已简化)
template <typename Obj>
Obj *Acquire()
{
    static_assert(
        !std::is_base_of_v<LLBC_Object, Obj>,
        "Obj can not create by ObjPool, as is derived form LLBC_Object");
    // ...
}

LLBC_Object 有自己的引用计数 + 自动释放池(Retain()/Release()/AutoRelease())生命周期模型,与 ObjPool 的 Acquire/Release 归还模型根本冲突,混用会导致双重释放。

如需在池中使用 LLBC_Object 派生类,应让对象继承 LLBC_PoolObj(而非 LLBC_Object),并通过 ObjPool 管理。


Q6:应该选 LLBC_Object(引用计数)还是 LLBC_ObjPool(对象池)?

两者解决不同问题,选择依据如下:

维度 LLBC_Object LLBC_ObjPool / LLBC_PoolObj
核心机制 引用计数 + 自动释放池 Acquire/Release 归还复用
适用场景 共享所有权、生命周期不确定的对象 高频创建销毁、生命周期明确的对象
性能 引用计数有原子操作开销 池化避免频繁 malloc/free,性能更高
线程安全 SafeRetain()/SafeRelease() 显式选择 LLBC_ObjPool(threadSafe=true) 或线程专属池
归还方式 Release() / AutoRelease() pool->Release(obj) 或 LLBC_GuardedPoolObj<T> RAII 自动归还

经验规则:网络层的 Packet、业务消息、临时数据结构等短生命周期对象优先用 ObjPool;需要在多处共享引用的业务对象(如场景中的实体)考虑 LLBC_Object。

对象池的 RAII 用法示例:

LLBC_ObjPool pool;
{
    // AcquireGuarded 返回 LLBC_GuardedPoolObj,离开作用域自动归还
    auto guardedObj = pool.AcquireGuarded<MyData>();
    guardedObj->DoSomething();
} // 此处自动 Release 回池

每个 llbc 线程还内置了线程专属池(无锁,性能最高):

// 当前线程的非线程安全池(性能最佳,只能在本线程用)
auto *obj = LLBC_ThreadSpecObjPool::UnsafeAcquire<MyData>();
// ...
LLBC_ThreadSpecObjPool::UnsafeRelease(obj);

参照

下一步