0
点赞
收藏
分享

微信扫一扫

协程库项目—协程类模块

笙烛 03-11 08:30 阅读 4
c++

协程类

ucontext_t结构体

头文件中定义的四个函数(getcontext(), setcontext(), makecontext(), swapcontext())和两个结构类型(mcontext_t, ucontext_t)在一个进程中实现用户级的线程切换。
其中,mcontext_t类型与机器相关,不透明;ucontext_t结构体至少包含以下几个域:

typedef struct ucontext {
    struct ucontext *uc_link;
    sigset_t         uc_sigmask;
    stack_t          uc_stack;
    mcontext_t       uc_mcontext;
    ...
} ucontext_t;

当当前上下文运行终止时,系统会恢复uc_link指向的上下文;uc_sigmask为该上下文中的阻塞信号集合;uc_stack为该上下文中使用的栈;uc_mcontext保存的上下文的特定机器表示,包括调用线程的特定寄存器等。

下面是这四个函数的详细介绍:

int getcontext(ucontext_t *ucp);

初始化ucp结构体,将当前的上下文保存到ucp中。

int setcontext(const ucontext_t *ucp);

设置当前的上下文为ucp,setcontext的上下文ucp应该通过getcontext或者makecontext取得。如果调用成功则不返回。如果上下文是通过调用getcontext()取得,程序会继续执行这个调用(从该上下文的状态开始继续执行,即调用getcontext处后接着执行)。如果上下文是通过调用makecontext取得,程序会调用makecontext函数的第二个参数指向的函数,如果func函数返回,则恢复makecontext第一个参数指向的上下文。如果uc_link为NULL,则线程退出。

void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);

makecontext修改通过getcontext取得的上下文ucp(这意味着调用makecontext前必须先调用getcontext)。然后给该上下文指定一个栈空间ucp->stack,设置后继的上下文ucp->uc_link。当上下文通过setcontext或者swapcontext激活后,执行func函数,argc为func的参数个数,后面是func的参数序列。当func执行返回后,继承的上下文被激活,如果继承上下文为NULL时,线程退出。

int swapcontext(ucontext_t *oucp, ucontext_t *ucp);

保存当前上下文到oucp结构体中,然后激活upc上下文。如果执行成功,getcontext返回0,setcontext和swapcontext不返回;如果执行失败,getcontext,setcontext,swapcontext返回-1,并设置对应的errno。

⾮对称模型

这里采用的是非对称模型,
保证⼦协程不能再创建新的协程,即协程不能嵌套调⽤,⼦协程只能与主线程进行切换。注意下图中子协程只能切换回主协程,不能创建新的子协程。
在这里插入图片描述

简化协程状态

只设置三种协程状态:就绪态、运⾏态和结束态,⼀个协程要么正在运⾏(RUNNING),要么准备运⾏(READY),要运⾏
结束(TERM)。
在这里插入图片描述

协程操作

协程创建操作
创建线程主协程:只需要将协程设置为当前运行协程,协程转为RUNING,获取当前上下文。
创建⽤户协程:则需要额外创建栈空间和绑定协程入口函数

/**
* @brief 线程主协程构造函数
* @attention ⽆参构造函数只⽤于创建线程的第⼀个协程,也就是线程主函数对应的协程,
* 这个协程只能由GetThis()⽅法调⽤,所以定义成私有⽅法
*/
Fiber::Fiber(){
 	SetThis(this);
 	m_state = RUNNING;
 	if (getcontext(&m_ctx)) {
 		Fzk_ASSERT2(false, "getcontext");
 	}
 	++s_fiber_count;
 	m_id = s_fiber_id++; // 协程id从0开始,⽤完加1
 	Fzk_LOG_DEBUG(g_logger) << "Fiber::Fiber() main id = " << m_id;
}

/**
* @brief 构造函数,⽤于创建⽤户协程
* @param[] cb 协程⼊⼝函数
* @param[] stacksize 栈⼤⼩
*/
Fiber::Fiber(std::function<void()> cb, size_t stacksize)
 : m_id(s_fiber_id++)
 , m_cb(cb) {
	 ++s_fiber_count;
 	m_stacksize = stacksize ? stacksize : g_fiber_stack_size->getValue();
 	m_stack = StackAllocator::Alloc(m_stacksize);
 	if (getcontext(&m_ctx)) {
 		Fzk_ASSERT2(false, "getcontext");
 	}
 	m_ctx.uc_link = nullptr;
 	m_ctx.uc_stack.ss_sp = m_stack;
 	m_ctx.uc_stack.ss_size = m_stacksize;
 	makecontext(&m_ctx, &Fiber::MainFunc, 0);
 	Fzk_LOG_DEBUG(g_logger) << "Fiber::Fiber() id = " << m_id;
}

协程间执行权转换操作

/// @brief 恢复协程运行
///恢复该协程的运行。
void Fiber::resume() {
    Fzk_ASSERT(m_state != TERM && m_state != RUNNING);
    SetThis(this);
    m_state = RUNNING;
    //涉及后面的协程调度,如果协程参与调度器调度,那么应该和调度器的主协程进行swap,而不是线程主协程
    // if (m_runInScheduler) {
    //     if (swapcontext(&(Scheduler::GetMainFiber()->m_ctx), &m_ctx)) {
    //         Fzk_ASSERT2(false, "swapcontext");
    //     }
    // } else {
    //      if (swapcontext(&(t_thread_fiber->m_ctx), &m_ctx)) {
    //          Fzk_ASSERT2(false, "swapcontext");
    //      }
    //  }
    //发生段错误,已解决,是新创建的子协程未保存其上下文,导致&m_ctx对未初始化对象进行取地址操作
    if (swapcontext(&(t_thread_fiber->m_ctx), &m_ctx)) {
              Fzk_ASSERT2(false, "swapcontext");
    }
}
/// @brief 当前协程让出执⾏权
///当前协程让出执⾏权, 当前协程的状态有两种情况:1、协程函数未执行完,更新为READY; 2、执行完更新为TERM
void Fiber::yield() {
    /// 协程运行完之后会自动yield一次,用于回到主协程,此时状态已为结束状态
    Fzk_ASSERT(m_state == RUNNING || m_state == TERM);
    SetThis(t_thread_fiber.get());
    if(m_state != TERM) {
        m_state = READY;
    }
    // 如果协程参与调度器调度,那么应该和调度器的主协程进行swap,而不是线程主协程
    // if(m_runInScheduler) {
    //     if(swapcontext(&m_ctx, &(Scheduler::GetMainFiber()->m_ctx))) {
    //         Fzk_ASSERT2(false, "swapcontext");
    //     }
    // } else {
    //      if (swapcontext(&m_ctx, &(t_thread_fiber->m_ctx))) {
    //          Fzk_ASSERT2(false, "swapcontext");
    //      }
    //  }
    if (swapcontext(&m_ctx, &(t_thread_fiber->m_ctx))) {
            Fzk_ASSERT2(false, "swapcontext");
    }
}

后续工作:实现协程调度类

为使得协程类能够通过调度器来运⾏,需要对已实现的协程类进行以下具体操作:

举报

相关推荐

0 条评论