<C++高性能后台服务器开发-Sylar>纯手搓多线程多协程调度记录

多线程及多协程调度管理

1.说明

源码戳这里

2. 线程模块 thread 源码戳这里

2.1 系统锁的封装

  • 目的:实现更细粒度的锁的模块封装
    • 信号量Semaphore
    • 普通锁、读写锁的模板类
      • 使用RAII特性封装的局部锁ScopedLockImpl
        • 构造时加锁,析构时解锁
        • 手动加锁,手动解锁
      • 使用RAII特性封装的读锁ReadScopedLockImpl
        • 构造时加锁,析构时解锁
        • 手动加锁,手动解锁
      • 使用RAII特性封装的读锁WriteScopedLockImpl
        • 构造时加锁,析构时解锁
        • 手动加锁,手动解锁
    • 互斥量Mutex
      • typedef ScopedLockImpl Lock;
    • 读写锁RWMutex
      • typedef ReadScopedLockImpl ReadLock;
      • typedef WriteScopedLockImpl WriteLock;
    • 自旋锁Spinlock
      • 自旋锁(Spin Lock)是一种基于忙等待的锁机制,它是一种轻量级的锁实现方式。与传统的阻塞锁不同,自旋锁在获取锁时不会主动阻塞线程,而是通过循环不断地尝试获取锁,直到成功获取为止
        • typedef ReadScopedLockImpl ReadLock;
        • typedef WriteScopedLockImpl WriteLock;
    • 原子锁CASLock
      • CAS(Compare And Swap)锁基于原子性操作,它通过比较内存值与预期值的方式来实现线程间的同步。如果当前内存值与预期值相等,则更新内存值为新值,否则不做任何操作
      • typedef ScopedLockImpl Lock;

2.2 线程类

  • 线程类Thread
    • 私有成员变量
      • 信号量m_semaphore的作用是在Thread构造函数在使用pthread_create创建线程时,保证run函数可以获取到cb
      •  // 线程id
        pid_t m_id = -1;
        // 线程
        pthread_t m_thread = 0;
        // 线程执行函数
        std::function<void()> m_cb;
        // 线程名称
        std::string m_name;
        // 信号量
        Semaphore m_semaphore;
        
    • 静态变量以及方法
      •   // 线程局部变量
          // <t_thread> -> 指向当前线程
          static thread_local Thread *t_thread = nullptr;
          static thread_local std::string t_thread_name = "DefaultThread";
          Thread *Thread::GetThis() {
              return t_thread;
          }
        
          const std::string &Thread::GetName() {
              return t_thread_name;
          }
        
          void Thread::SetName(const std::string &name) {
              if (t_thread) {
                  t_thread->m_name = name;
              }
              t_thread_name = name;
          }
        
  • 线程核心工作函数run:首先将t_thread指定,然后进行线程名称及id的获取,最后执行一次callback结束
    •   void *Thread::run(void *arg) {
            Thread *thread = (Thread *) arg;
            t_thread = thread;
            t_thread_name = thread->m_name;
            thread->m_id = cherry::GetThreadId();
            pthread_setname_np(pthread_self(), thread->m_name.substr(0, 15).c_str());
      
            std::function<void()> cb;
            cb.swap(thread->m_cb);
      
            thread->m_semaphore.notify();
      
            cb();
            return nullptr;
      

3.协程模块 源码戳这里

3.1 说明

核心内容是基于ucontext实现.

由于调度器Scheduler管理协程,因此每个工作的协程上下文切换均需要交给调度器协程来调度.

这就涉及到了一个问题:是否使用调度器所在协程的线程呢,因此使用到了一个booluse_caller

use_caller为true,则将调度器所在线程也纳入到线程池的线程之中,否则就是独立的

3.2 Fiber

  • 私有成员变量
    •     /// 协程id
          uint64_t m_id = 0;
          /// 协程运行栈大小
          uint32_t m_stacksize = 0;
          /// 协程状态
          State m_state = INIT;
          /// 协程上下文
          ucontext_t m_ctx;
          /// 协程运行栈指针
          void *m_stack = nullptr;
          /// 协程运行函数
          std::function<void()> m_cb;
      
  • 协程状态变量枚举
    •    enum State {
             /// 初始化状态
             INIT,
             /// 暂停状态
             HOLD,
             /// 执行中状态
             EXEC,
             /// 结束状态
             TERM,
             /// 可执行状态
             READY,
             /// 异常状态
             EXCEPT
            };
      
  • 静态变量
    • static thread_local Fiber *t_fiber = nullptr; -> 当前线程运行的协程
    • static thread_local Fiber::ptr t_threadFiber = nullptr; -> t_threadFiber:每个线程所创建的第一个协程,它没有栈空间
  • 方法
    • uint64_t Fiber::GetFiberId() -> 获取当前协程的id 用于日志打印时用
    • Fiber::SetThis(Fiber *f) -> 将当前运行的协程设置为f
    • Fiber::Fiber() -> 默认构造函数 注意:它只负责t_threadFiber的创建,是私有方法,不会被外部调用
    • Fiber::Fiber(std::function<void()> cb, size_t stacksize, bool use_caller) -> 使用cb构造协程,stacksize指定了协程栈的大小,如果use_caller为true(调度协程所在线程将会纳入到线程池范围当中),则会将当前协程上下文指定为Fiber::MainFunc,否则将会指定为Fiber::CallerMainFunc
    • Fiber::~Fiber() -> 析构函数,如果栈不为空(说明为用户创建的协程),则继续判断:当前协程如果状态为TERM或者EXCEPT或者INIT,则将栈析构掉.如果是它只负责t_threadFiber的话则直接将它只负责t_threadFiber设为nullptr
    • Fiber::ptr Fiber::GetThis() 返回当前运行的协程,如果当前线程没有协程,则会调用无参的构造函数创建t_threadFiber,并将当前运行的协程指定为t_threadFiber:t_fiber = t_threadFiber
    • Fiber::swapIn() Fiber::swapOut() 与调度器协程交换上下文
    • Fiber::call() Fiber::back() 与主协程交换上下文
    • YieldToReady() -> 获取当前正在执行的协程,将其状态设置为READY,然后保存其上下文,与调度器协程上下文交换
    • Fiber::MainFunc(): -> 执行cb(),并设置其状态为TREM or 出现异常 设置为EXECPT -> 返回调度器协程
    • Fiber::CallerMainFunc(): -> 执行cb(),并设置其状态为TREM or 出现异常 设置为EXECPT -> 返回t_threadFiber

4.调度器模块 源码戳这里

4.1 说明: 调度器模块是io调度的基类,定义了最基本的调度过程

4.2 Scheduler

  • 私有成员变量
    •     private:
          /// Mutex
          MutexType m_mutex;
          /// 线程池
          std::vector<Thread::ptr> m_threads;
          /// 待执行的协程队列
          std::list<FiberAndThread> m_fibers;
          /// use_caller为true时有效, 调度协程
          Fiber::ptr m_rootFiber;
          /// 协程调度器名称
          std::string m_name;
          protected:
          /// 协程下的线程id数组
          std::vector<int> m_threadIds;
          /// 线程数量
          size_t m_threadCount = 0;
          /// 工作线程数量
          std::atomic<size_t> m_activeThreadCount = {0};
          /// 空闲线程数量
          std::atomic<size_t> m_idleThreadCount = {0};
          /// 是否正在停止
          bool m_stopping = true;
          /// 是否自动停止
          bool m_autoStop = false;
          /// 主线程id(use_caller)
          int m_rootThread = 0;
      
  • 静态变量
    • static thread_local Scheduler *t_scheduler调度器
    • static thread_local Fiber *t_scheduler_fiber调度器所在协程
  • Scheduler::Scheduler(size_t threads, bool use_caller, std::string name)
    • use_caller为true
    • 创建t_threadFiber
    • 线程数量-1
    • 设置t_scheduler为当前Scheduler
    • 创建rootFiber,std::make_shared<Fiber>(std::bind(&Scheduler::run, this), 0, true);
    • 执行rootThread
    • 指定t_scheduler_fiber为rootFiber
    • use_caller为false
      • m_rootThread = -1; 表明调度器没有纳入到当前线线程池中
  • Scheduler::~Scheduler()
    • 如果正在停止
      • 如果t_scheduler为当前Scheduler,则析构
  • Scheduler *Scheduler::GetThis() -> 获取调度器
  • Fiber *Scheduler::GetMainFiber() -> 获取调度器所在协程
  • Scheduler::start() -> 创建线程池,执行run函数
  • Scheduler::stop() -> 设置m_autoStop为ture,表明要停止了
    • 如果m_rootFiber存在,并且thread数量已经为0,并且m_rootFiber为INIT或者TERM
      • m_stopping为true,表明可以停止
        • if (stopping()) 检查没有协程 直接可以return
      • tickle各个线程
      • tickle m_rootFiber
      • 如果m_rootFiber不满足停止条件 调度m_rootFiber->call() 将执行权交给t_threadFiber
      • 所有线程join
  • Scheduler::setThis() -> 设置t_scheduler = this;
  • Scheduler::tickle() -> tickle 用于通知各个线程执行时有任务需要处理,将在继承类iomanager中实现
  • Scheduler::stopping() -> 判断当前是否可以停止
  • Scheduler::idle()-> run函数中如果没有找到可以运行的协程或者callback,则会执行idle
  • Scheduler::run()-> 核心函数
    • 首先将线程内的t_scheduler指定为全局唯一的调度器
    • 如果当前线程不是t_scheduler所在线程,则初始化一个thread_local的t_scheduler_fiber
    • 创建idle_fiber和一个cb_fiber指针
    • FiberAndThread ft;用于寻找运行协程或者callback
    • while(true)
    • 重置ft
      • while (it != m_fibers.end())
        • 如果it指定了要在id为it->thread上运行,并且当前线程id不是要求的线程id
          • ++it; tickle_me = true;
        • 如果是fiber并且正在执行
          • ++it;
        • 获取ft = *it;
    • 后续执行流程不存在多线程冲突情况,因为只在自身线程内执行逻辑,
      如果use_caller为true的话,协程执行完毕后会与thread_local的t_threadFiber交换,改变的计数变量也均为原子类型
      如果use_caller为false的话,协程执行完毕后会与与thread_local的t_scheduler_fiber交换,也不会有线程冲突问题
    • 如果需要tickle,则tickle
    • 如果是协程
      • if (ft.fiber && (ft.fiber->getState() != Fiber::TERM && ft.fiber->getState() != Fiber::EXCEPT))
      • 主协程 -> ft.fiber
      • 如果执行之后 ft.fiber->getState() == Fiber::READY
        • 再次schedule
      • 如果执行之后ft.fiber->getState() != Fiber::TERM && ft.fiber->getState() != Fiber::EXCEPT
        • ft.fiber->m_state = Fiber::HOLD;
      • ft.reset();
    • 如果是cb
      • 创建cb并且执行与fiber类似的逻辑
    • 没有协程或者cb
      • 执行idle_fiber
  • 30
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值