TARS 线程模型与 Servant 生命周期(总结自AI对话)
起源:业务代码里的第一行注册
我们在业务代码里看到了这样一行:
void MyServiceApp::initialize()
{
addServant<MyServiceServantImp>(
ServerConfig::Application + "." + ServerConfig::ServerName + ".MyServiceServantObj"
);
}
第一个疑问冒出来了: addServant 是什么?是add到哪里?会初始化吗?
第一层:addServant 是什么
顺着代码往下找,发现 addServant 不是全局函数,而是 Application 类的成员函数模板。
Application 是 TARS 框架的核心类,就是 main 函数里 g_app 的类型。它在初始化阶段调用业务代码的 initialize(),业务代码在里头向框架"注册"Servant。
// Application.h —— Application::addServant 是包装层
template<typename T>
void addServant(const string &id)
{
// 只有一个参数 id,this 和 check 是自动补的
ServantHelperManager::getInstance()->addServant<T>(id, this, true);
}
这里产生第二个疑问: this 是什么?为什么要传 check = true?
this就是g_app,即 Application 单例指针check = true表示要校验配置文件里是否声明了对应的 adapter,没有就抛异常
所以 Application::addServant<T>(id) 只是个便捷入口,它帮你补上了 this(框架对象)和 check(配置校验),然后转发给真正的执行者 ServantHelperManager::addServant<T>(id, this, true)。
第二层:ServantHelperManager::addServant 做了什么
找到真正的执行者:
// ServantHelper.h —— 底层真正执行
template<typename T>
void addServant(const string &id, Application *application, bool check = false)
{
if (check && _servant_adapter.end() == _servant_adapter.find(id))
{
// 校验:id 必须在配置文件的 adapter 列表里存在
throw runtime_error("[TARS]ServantHelperManager::addServant " + id + " not find adapter.");
}
// 关键:将 creator 存入 map
_servant_creator[id] = new ServantCreation<T>(application);
}
第三个疑问: _servant_creator 是什么?ServantCreation<T> 是什么?为什么 application 要传进去?
第三层:为什么要"工厂模式"—— ServantCreation
顺着 _servant_creator 找到它的类型:
// ServantHelper.h
map<string, ServantHelperCreation*> _servant_creator;
是个 map,key 是 id(比如 "MyApp.MyServer.MyServantObj"),value 是 ServantHelperCreation*。
而 ServantCreation<T> 是它的子类:
template<class T>
struct ServantCreation : public ServantHelperCreation
{
ServantCreation(Application *application) : _application(application) {}
ServantPtr create(const string &s)
{
T *p = new T;
p->setName(s);
p->setApplication(_application);
return p;
}
Application *_application;
};
疑问又来了:
3.1 为什么用工厂模式,而不是直接 new?
因为注册发生在 main() 阶段,但请求处理发生在 Handle 线程启动之后。这两步之间有时间差:
main() 阶段:
addServant<MyServiceServantImp>("obj")
→ new ServantCreation<MyServiceServantImp>(g_app) ← 只是把 creator 存起来
Handle 线程启动时(晚得多):
→ 调用 creator->create("obj")
→ new MyServiceServantImp ← 这个时候才真正 new
注册阶段只负责把"怎么 new"的配方(creator)存好,具体创建延迟到 Handle 线程启动时才执行。
3.2 三行代码分别是什么含义?
第一行——构造函数:
ServantCreation(Application *application) : _application(application){}
接受一个 Application* 参数,用初始化列表把它存进成员变量 _application。这里 this 指向 g_app 指针。
第二行——create() 工厂方法:
ServantPtr create(const string &s) { T *p = new T; p->setName(s); p->setApplication(_application); return p; }
工厂方法,每次调用都 new 一个新的 T 实例,然后:
setName(s)— 设置 servant 名称(adapter 名字)setApplication(_application)— 把g_app指针绑进来,这样 Servant 内部能访问全局配置
第三行——成员变量:
Application *_application;
存储 g_app 指针,供 create() 使用。就是个普通的成员变量。
整体含义一句话: 注册时存下 g_app 指针,启动时 new 出 Servant 实例并把 g_app 绑定进去。
几个要点:
- 注册阶段没有线程,所以不能直接 new——工厂把"配方"先存起来
- Handle 线程启动时,每个线程独立调用
creator->create(),自然就拿到了自己的实例 - 不需要额外同步——
_servant_creatormap 只在initialize()阶段写(单线程),读都在 Handle 线程启动之后,没有竞争
换句话说,工厂模式在这里解决的不只是"怎么 new",而是**"在哪条线程 new"**的问题。
第四层:Handle 线程模型——线程数、实例数、线程安全
这是最核心的问题,也是今天下午讨论最多的。
4.1 有多少个 Handle 线程?
默认 5 个,可以在配置文件中修改。这个数量是进程级别的,同一个进程里所有 Handle 线程共享代码段、全局变量等。
4.2 Servant 实例和线程是什么关系?
每个 Handle 线程启动时,TARS 框架遍历 _servant_creator 里的所有 creator,调用 create():
Handle 线程 1 启动
→ new MyServiceServantImp 实例 A
→ setApplication(g_app)
Handle 线程 2 启动
→ new MyServiceServantImp 实例 B
→ setApplication(g_app)
... 线程 3、4、5 同理
结论:线程数 = Servant 实例数。5 个 Handle 线程 = 5 个独立的 Servant 实例。
4.3 成员变量是线程安全的吗?
这取决于成员变量在哪里:
| 成员变量 | 存储位置 | 线程安全 | 原因 |
|---|---|---|---|
|
|
每个 Servant 实例自己的 |
✅ 安全 |
实例 A 和实例 B 不共享这块内存 |
|
|
每个 Servant 实例自己的 |
✅ 安全 |
同上 |
|
|
每个线程自己的栈 |
✅ 安全 |
|
|
|
进程共享 |
⚠️ 关注 |
连接池内部有锁,外部不要乱动 |
|
static 变量 |
进程共享 |
⚠️ 关注 |
所有线程可见 |
结论: 成员变量是线程隔离的,不需要加锁。只有访问 g_app 或 static 变量时才需要考虑同步。
第五层:完整流程回顾(从疑问到闭环)
① main() 启动
g_app.main(argc, argv)
→ 调用业务 MyServiceApp::initialize()
② 注册阶段(initialize 内部)
addServant<MyServiceServantImp>("App.Server.MyServantObj")
→ Application::addServant<T>(id)
→ ServantHelperManager::addServant<T>(id, this, true)
→ _servant_creator["App.Server.MyServantObj"] = new ServantCreation<T>(g_app)
目的:把 "怎么 new" 的配方存起来,延迟到 Handle 线程启动时再执行
③ Handle 线程启动(N 个线程)
线程 1 → creator->create("obj") → new MyServiceServantImp 实例 A → setApplication(g_app)
线程 2 → creator->create("obj") → new MyServiceServantImp 实例 B → setApplication(g_app)
线程 3 → ...
线程 4 → ...
线程 5 → ...
④ 请求处理
客户端请求 → TARS 框架分发到某个 Handle 线程
→ 该线程用自己的 Servant 实例处理
→ 成员变量无竞争,无需加锁
第六层:几个容易踩的坑
❌ 误区1:ServantImp 是单例
ServantImp 不是单例。每个 Handle 线程有自己独立的实例,构造函数会跑 N 次(= 线程数)。如果想在进程级别共享数据,不要用成员变量,要用 static 或全局变量——但这通常意味着你要自己处理线程安全。
❌ 误区2:给所有成员变量加锁
成员变量如果只在单个 Handle 线程内访问,加锁反而增加开销且容易引入死锁。先搞清楚数据流,再决定是否加锁。
❌ 误区3:注册时创建实例
注册阶段只是把 creator 存起来,不是创建实例的时机。在 initialize() 里写业务逻辑、读配置都可以,但不要试图在这里做请求处理——Handle 线程还没启动,请求根本进不来。
附录:关键数据结构一览
| 类型 | 位置 | 作用 |
|---|---|---|
|
|
框架层 |
全局单例 |
|
|
框架层 |
管理所有 Servant 的 creator,核心是 |
|
|
框架层 |
工厂模板, |
|
|
业务层 |
实现具体业务逻辑,继承框架生成的 Servant 基类 |
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)