多线程及多协程调度管理
1.说明
2. 线程模块 thread 源码戳这里
2.1 系统锁的封装
- 目的:实现更细粒度的锁的模块封装
- 信号量
Semaphore
- 普通锁、读写锁的模板类
- 使用RAII特性封装的局部锁
ScopedLockImpl
- 构造时加锁,析构时解锁
- 手动加锁,手动解锁
- 使用RAII特性封装的读锁
ReadScopedLockImpl
- 构造时加锁,析构时解锁
- 手动加锁,手动解锁
- 使用RAII特性封装的读锁
WriteScopedLockImpl
- 构造时加锁,析构时解锁
- 手动加锁,手动解锁
- 使用RAII特性封装的局部锁
- 互斥量
Mutex
- typedef ScopedLockImpl Lock;
- 读写锁
RWMutex
- typedef ReadScopedLockImpl ReadLock;
- typedef WriteScopedLockImpl WriteLock;
- 自旋锁
Spinlock
- 自旋锁(Spin Lock)是一种基于忙等待的锁机制,它是一种轻量级的锁实现方式。与传统的阻塞锁不同,自旋锁在获取锁时不会主动阻塞线程,而是通过循环不断地尝试获取锁,直到成功获取为止
- typedef ReadScopedLockImpl ReadLock;
- typedef WriteScopedLockImpl WriteLock;
- 自旋锁(Spin Lock)是一种基于忙等待的锁机制,它是一种轻量级的锁实现方式。与传统的阻塞锁不同,自旋锁在获取锁时不会主动阻塞线程,而是通过循环不断地尝试获取锁,直到成功获取为止
- 原子锁
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
管理协程,因此每个工作的协程上下文切换均需要交给调度器协程来调度.
这就涉及到了一个问题:是否使用调度器所在协程的线程
呢,因此使用到了一个bool
值use_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)
-> 将当前运行的协程设置为fFiber::Fiber()
-> 默认构造函数 注意:它只负责t_threadFiber的创建,是私有方法,不会被外部调用Fiber::Fiber(std::function<void()> cb, size_t stacksize, bool use_caller)
-> 使用cb构造协程,stacksize指定了协程栈的大小,如果use_caller为true(调度协程所在线程将会纳入到线程池范围当中),则会将当前协程上下文指定为Fiber::MainFunc,否则将会指定为Fiber::CallerMainFuncFiber::~Fiber()
-> 析构函数,如果栈不为空(说明为用户创建的协程),则继续判断:当前协程如果状态为TERM或者EXCEPT或者INIT,则将栈析构掉.如果是它只负责t_threadFiber的话则直接将它只负责t_threadFiber设为nullptrFiber::ptr Fiber::GetThis()
返回当前运行的协程,如果当前线程没有协程,则会调用无参的构造函数创建t_threadFiber,并将当前运行的协程指定为t_threadFiber:t_fiber = t_threadFiberFiber::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
- m_stopping为true,表明可以停止
- 如果m_rootFiber存在,并且thread数量已经为0,并且m_rootFiber为INIT或者TERM
Scheduler::setThis()
-> 设置t_scheduler = this;Scheduler::tickle()
-> tickle 用于通知各个线程执行时有任务需要处理,将在继承类iomanager中实现Scheduler::stopping()
-> 判断当前是否可以停止Scheduler::idle()
-> run函数中如果没有找到可以运行的协程或者callback,则会执行idleScheduler::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;
- 如果it指定了要在id为it->thread上运行,并且当前线程id不是要求的线程id
- 后续执行流程不存在多线程冲突情况,因为只在自身线程内执行逻辑,
如果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