协程退出与智能指针
该问题是由于智能指针的引用计数问题引起的。fiber使用的智能指针无法析构导致fiber类无法析构。
问题描述
实现sylar协程模块时,遇到了协程退出的问题。 sylar的协程是有栈协程,且只支持主协程与子协程之间的切换。 当一个子协程任务函数执行完毕,需要退出时,需要yeildToHold
(swapOut
)将控制权转回主协程。但如果是子协程主动yeild,将导致任务函数无法完全完成,对应的协程类的智能指针计数永远大于等于1。如代码所示:
cpp
// 协程 执行函数
void Fiber::MainFunc() {
Fiber::ptr cur_fiber = GetThisFiber();
M_SYLAR_ASSERT(cur_fiber);
// 任务函数执行逻辑
// ...
// 协程退出逻辑
cur_fiber->swapOut();
// 添加assert防止销毁协程重新进入
M_SYLAR_ASSERT2(false, "fiber has already been destoried, but still swaped in.");
}
如代码所示,Fiber::MainFunc()
中持有的cur_fiber
是当前协程的智能指针。由于cur_fiber
已经swapOut
切出了,导致智能指针(cur_fiber
)计数永远加一,类永远无法析构。
问题分析
该问题是由于智能指针的引用计数问题引起的。使用的智能指针无法析构导致fiber
类无法析构。需要解决函数内的智能指针析构的问题。
问题解决
既然是智能指针引发的问题,那么不用智能指针不就解决了。如代码所示:
cpp
// 协程 执行函数
void Fiber::MainFunc() {
Fiber::ptr cur_fiber = GetThisFiber();
M_SYLAR_ASSERT(cur_fiber);
// 任务函数执行逻辑
// ...
// 协程退出逻辑
auto raw_ptr = cur_fiber.get(); // 获取类的指针
cur_fiber.reset(); // 使fiber对象引用计数减一
raw_ptr->swapOut();
M_SYLAR_ASSERT2(false, "fiber has already been destoried, but still swaped in.");
}
通过使用raw_ptr
裸指针,同时重置cur_fiber
使得引用计数减一,即可解决智能指针无法销毁的问题。
新的问题
但是这样不会导致raw_ptr
变成野指针吗? 其实不会的。
cpp
void Fiber::MainFunc() {
Fiber::ptr cur_fiber = GetThisFiber();
M_SYLAR_ASSERT(cur_fiber);
// 任务函数执行逻辑
// ...
// runner_fiber 执行完毕
// 协程退出逻辑
auto raw_ptr = cur_fiber.get(); // 获取类的指针
cur_fiber.reset(); // 使fiber对象引用计数减一
raw_ptr->swapOut();
M_SYLAR_ASSERT2(false, "fiber has already been destoried, but still swaped in.");
}
void runner_fiber () {
m_sylar::Logger::ptr self_logger = M_SYLAR_GET_LOGGER_ROOT();
m_sylar::Logger::ptr system_logger = M_SYLAR_LOG_NAME("system");
M_SYLAR_LOG_INFO(system_logger) << "runner_fiber begin";
m_sylar::Fiber::YieldToHold(); // 交出执行权
M_SYLAR_LOG_INFO(system_logger) << "runner_fiber end";
}
void runner_thread() {
// ...
m_sylar::Fiber::ptr newFiber (new m_sylar::Fiber(runner_fiber)); // 这里有子fiber的一个智能指针。
newFiber->swapIn(); // 第一次进入fiber
M_SYLAR_LOG_INFO(self_logger) << "runner_thread swapIn";
newFiber->swapIn(); // 第二次进入fiber,此次进入后newFiber应该执行完毕
// ...
}
runner_fiber
执行完毕时,runner_thread
中仍持有一个newFiber
,所以cur_fiber.reset();
并不会导致子协程类的析构。 使用gdb调试也印证了这一点。 如果从一开始就用裸指针也同样可以解决问题。
结论与反思
从此次遇到的问题可以做个总结:对于获取的智能指针使用上述方法,不会导致野指针的产生(除非你把它传参出去了)。