Linux 64位 C++协程池原理分析及代码实现

news2024/11/26 22:40:58

导语

本文介绍了协程的作用、结构、原理,并使用C++和汇编实现了64位系统下的协程池。文章内容避免了协程晦涩难懂的部分,用大量图文来分析原理,适合新手阅读学习。

GitHub源码

1. Web服务器问题

现代分布式Web后台服务逻辑通常由一系列RPC请求组成,若串行则耗时比较长。

此时一般都会使用线程池并行运行RPC请求,如图中GetData函数

假设请求数据包不大,那么可假设GetData耗时组成如下图所示。在非阻塞读情况下,CPU将在Wait环节空转浪费资源(不断地read,得到返回码-1)。

2. 协程的引入

有没有办法只用一个线程并行执行GetData呢?答案是:可以!我们假设有3个并行的GetData任务,下图线程1通过跳转控制流,减少CPU资源浪费。执行流为①~⑦,在Wait阶段则跳到其他任务如①~⑤。运行结束后也跳到其他任务如⑥~⑦。通过这种方式,3个GetData能用一个线程以52ms的耗时并行执行。

如果GetData任务可以被这样分配,则可以减少线程切换的消耗。因为协程的调度是线程内用户态执行的,CPU消耗非常小。

相关视频推荐

协程!协程!协程!给你一个吊打面试官的机会!

用ntyco来解决,大块数据传输,连续包处理接收

协程在 reactor 网络模型中的应

免费学习地址:c/c++ linux服务器开发/后台架构师

需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

3. 协程的原理

从上文可知,协程之间的切换本质是函数的跳转,即如何让正在执行的函数跳转到另一个新的函数上,以及下次如何又跳转回来。如下面代码所示:

void func1() {
   printf("① 跳转到func2");
   Coroutine::CoYield(); // 通过该函数跳到func2
   printf("③ func2跳转回func1");
}

void func2() {
   printf("② func2执行完毕");
}

要实现这种能力,需要结合汇编知识。首先研究如下简单函数的汇编语言

#include <iostream>
using namespace std;

class Object {
public:    
   int val[12];
};

int func(Object *pObj1, Object *pObj2) {     
   pObj1->val[0] = 1;
   pObj1->val[11] = 11;
   pObj2->val[0] = 2;
   pObj2->val[11] = 12;
   int arr[100];
   arr[0] = 3;
   arr[99] = 99;
   return pObj1->val[0];
}

int main() {
   Object obj, obj2;
   int a = func(&obj, &obj2);
   return a;
}

下面看看在64位系统汇编中,func函数是如何执行的。

push %rbp是进入func函数执行的第一个指令,作用是把rbp的地址压到栈顶。因为rsp始终指向栈顶,所以压栈后,rsp的地址下移8字节。rdi和rsi相差48个字节,该空间被class Object内的int val[12]占用。

前两个指令让rbp指向rsp往下296字节的位置。后面两个指令把rdi和rsi地址保存在最下面。

为什么rsp下移296字节?首先,上述代码使用了临时变量int arr[100],需要有400个字节的栈空间;其次,x64系统存有128字节的红色区域可使用;最后,rdi和rsi地址共占16字节。因此,rbp到红色区域底部的空间一共是 288 + 8 + 104 + 8 + 8 = 416字节。

接下来才开始执行func函数第一行代码,给val[0]赋值。

然后分别给pObj1和pObj2的成员变量赋值

接下来给临时变量arr赋值

最后让eax指向返回值,恢复函数栈的栈底和栈顶。

4. 协程的结构

从前面我们知道,每个函数在内存中都有栈顶rsp和栈底rbp。这两个值决定了函数可操作的内存范围,如下图所示

既然协程切换是从一个函数切换到另一个函数,那么就需要知道两个函数的rbp和rsp。然而,函数的rbp和rsp是执行时设定的,代码层面难以获得。
既然如此,我们可以实现腾出空间,让函数在预期的rbp和rsp内。定义一个类如下:

class Coroutine {
    void* m_pRegister[14];
    char m_pStack[1024];
    std::function<void()> m_func;
};

那么在内存模型中,该类的布局如下所示

这样的协程在能被使用前需要做初始化,如下图所示

在其他协程切换过来时,cpu寄存器可按m_pRegister预设的地址赋值,开始执行DoWork函数,函数代码如下:

static void Coroutine::DoWork(Coroutine *pThis) {
    pThis->m_func();
    pThis->Yield(); // 转让控制流给同线程的其他协程
}

由于是静态函数,需令参数pThis为协程地址。所以,初始化时需要设置m_pRegister中的rdi为this。上述第二行代码执行时,rbp会设为this。所以执行m_func时,如下图所示:

5. 协程间的切换

下面以Coroutine1切换到Coroutine2为例。主要分为两步:
1. 保存Coroutine1的上下文

2. 加载Coroutine2的上下文

切换代码可见源代码Coroutine::Switch

6. 协程池的实现

本文实现协程池比较简单,初始化创建线程并设置thread_local变量以保存协程队列状态。并且,每个线程额外创建一个main协程用作Guard。

在执行时,每个线程通过轮询的方式切换协程,若协程无任务则尝试CAS获取Job,否则直接执行已有Job。当Job执行完或主动CoYield时,切换到下一个协程。

为了避免CAS空转,在没有任务时会阻塞休眠。当任务来临时则Notify所有线程的协程。

7. 源代码

example.cpp

/**
 * @file example.cpp
 * @author souma
 * @brief 使用协程池的示例,编译命令如下
 * g++ example.cpp coroutine.cpp -lpthread -O3
 * @version 0.1
 * @date 2023-06-06
 * 
 * @copyright Copyright (c) 2023
 * 
 */
#include <iostream>
#include <array>
#include "coroutine.h"

using namespace std;
using namespace comm;

void func(const string &sTaskName, uint32_t uWaitSeconds) {
    printf("[%ld] [%s start], wait seconds[%u]\n", time(nullptr), sTaskName.c_str(), uWaitSeconds);
    time_t iStartSec = time(nullptr);
    // 默认可用65535字节的栈内存,具体可看CO_STACK_SIZE
    uint32_t uArrSize = 65535/4;
    int arr[uArrSize];
    while (time(nullptr) - iStartSec < uWaitSeconds) {
        // 操作栈内存
        for (int i = 0; i < uArrSize; ++i) {
            arr[i] = i;
        }

        // 切换控制流
        printf("[%ld] [%s] -> [协程池]\n", time(nullptr), sTaskName.c_str());
        usleep(100);
        Coroutine::CoYield(); // 只需这一个函数即可切换控制流
        printf("[%ld] [协程池] -> [%s]\n", time(nullptr), sTaskName.c_str());
    }

    // 检查栈内存是否正确
    for (int i = 0; i < uArrSize; ++i) {
        if (arr[i] != i) {
            printf("栈内存错误\n");
            exit(-1);
        }
    }
    printf("[%ld] [%s end], expect_timecost[%d], real_timecost[%ld]\n", time(nullptr), sTaskName.c_str(), uWaitSeconds, time(nullptr) - iStartSec);
}

int main() {
    // 如果想当线程池用,可以令第一个参数为线程数,第二个参数为1。
    // 在该场景下,使用小线程大协程不仅CPU消耗低,整体耗时也很低,可以自行测试。
    CoroutinePool oPool(2, 300);
    oPool.Run();

    time_t iStartTime = time(nullptr);
    const int iTaskCnt = 400;
    vector<shared_ptr<Future>> vecFuture;
    for (int i = 0; i < iTaskCnt; ++i) {
        // 模拟GetData中的Wait环节, 1 ~ 5秒等待
        shared_ptr<Future> pFuture = oPool.Submit([i](){func("Task" + to_string(i), random() % 5 + 1);});
        if (pFuture != nullptr) {
            vecFuture.emplace_back(pFuture);
        }
    }
    
    // 阻塞等待所有Task完成
    for (auto it = vecFuture.begin(); it != vecFuture.end(); ++it) {
        (*it)->Get();
    }

    printf("demo's finished, time cost[%ld]\n", time(nullptr) - iStartTime);
    return 0;
}

coroutine.h

/**
 * @file coroutine.h
 * @author souma
 * @brief 多线程无栈式协程池,请不要用-O0编译否则会产生coredump
 * @version 0.1
 * @date 2023-06-06
 * 
 * @copyright Copyright (c) 2023
 * 
 */
#pragma once
#include <functional>
#include <memory>
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <signal.h>
#include <pthread.h>
#include <condition_variable>
#include <unistd.h>

namespace comm {
    class Future;
    class CoroutinePool;
    class Coroutine;
    struct CoroutinePoolCtx;
    struct CoroutineTaskCtx;


    struct CoroutinePoolCtx {
        std::vector<std::shared_ptr<Coroutine>> m_vecCoroutine;
        std::shared_ptr<Coroutine> m_pMainCoroutine;
        uint32_t m_uCursor;
        uint32_t m_uWorkCnt;
    };

    struct CoroutineTaskCtx {
        std::function<void()> m_userFunc;
        std::shared_ptr<Future> m_pFuture;
    };

    // class ArraySyncQueue start
    template <class T>
    class ArraySyncQueue {
    public:
        ArraySyncQueue(uint32_t uCapacity, uint32_t uSleepUs = 100, uint32_t uRetryTimes = 3);
        bool Push(T *pObj);
        T* Pop();
        inline bool IsFull() const { return m_uPushCursor == m_uPopCursor - 1 || (m_uPopCursor == 0 && m_uPushCursor == m_vecQueue.size() - 1); }
        bool IsEmpty() const { return m_uPopCursor == m_uPushCursor; }

        ~ArraySyncQueue();

    private:
        uint32_t GetNextCursor(uint32_t uCursor);
    private:
        std::vector<T*> m_vecQueue;
        uint32_t m_uPushCursor = 0;
        uint32_t m_uPopCursor = 0;
        uint32_t m_uSleepUs;
        uint32_t m_uRetryTimes;
    };
    // class ArraySyncQueue end

    // class Coroutine start
    class Coroutine {
    public:
        
        friend class CoroutinePool;

        /**
         * @brief 调用该函数将执行流交给其他协程,仅在协程池环境下有效
         * 
         * @return true:协程切换成功, false:不在协程池环境中运行
         */
        static bool CoYield();
        
        Coroutine(const Coroutine &) = delete;
        Coroutine(Coroutine &&) = delete;
        Coroutine & operator=(const Coroutine &) = delete;
        Coroutine & operator=(Coroutine &&) = delete;

    private:
        // 4096是预留给库使用的栈内存大小,后者是留给用户使用的栈内存大小
        constexpr static uint32_t CO_STACK_SIZE = 4096 + 65535; 

        Coroutine();

        /**
         * @brief 当前协程是否绑定了任务
         *
         * @return true:是
         */
        inline bool HasTask() const { return m_pTaskCtx != nullptr; }

        /**
         * @brief 两个协程切换,从pPrev切换到pNext
         */
        static void Switch(Coroutine *pPrev, Coroutine *pNext);

        /**
         * @brief 将控制流转给同线程的其他协程
         */
        void Yield();

        /**
         * @brief 这个是给main协程用的
         */
        void Register();

        /**
         * @brief 这个是给执行用户任务的协程用的
         */
        void Register(std::shared_ptr<CoroutineTaskCtx> pTaskCtx);

        /**
         * @return CoroutinePoolCtx& 当前线程的协程上下文
         */
        static CoroutinePoolCtx & GetCtx();

        /**
         * @brief 让当前线程的cursor往后移,轮询协程
         */
        static void MoveCursor();
        
        /**
         * @brief 协程包一层的函数
         */
        static void DoWork(Coroutine *pThis);

        /**
         * 
         * @return void* 获得自建rsp地址
         */
        void* GetRsp();

        /**
         * 保存寄存器的值到m_pStack中
         */
        void SaveReg();

    private:
        void* m_pRegister[14];
        char m_pStack[CO_STACK_SIZE];
        std::shared_ptr<CoroutineTaskCtx> m_pTaskCtx;
    };
    // class Coroutine end

    // class CoroutinePool start
    class CoroutinePool {
    public:
        friend class Coroutine;
        /**
         * @brief 建立一个多线程协程池,即创建uThreadCnt个线程,每个线程含有uCoroutineCnt个协程
                  调用Run开始运行,调用Stop或直接析构结束
         * @param uThreadCnt 线程数,小于1则为1
         * @param uCoroutineCnt 每个线程的协程数,小于1则为1
         * @param uJobQueueSize 总任务队列大小,小于1则为1
         */
        CoroutinePool(uint32_t uThreadCnt, uint32_t uCoroutineCnt, uint32_t uJobQueueSize = 1024000);

        /**
         * @brief 线程安全,可重入
         * @return true:正常
         */
        bool Run();

        /**
         * @brief 停止协程池 (会先保证池中任务完成再停止),线程安全可重入
         * 
         */
        void Stop();

        /**
         * @param userFunc 用户函数
         * @return std::shared_ptr<Future>  nullptr:协程池队列满了,提交不了
         */
        std::shared_ptr<Future> Submit(const std::function<void()> &userFunc);

        ~CoroutinePool();
        CoroutinePool(const CoroutinePool &) = delete;
        CoroutinePool(CoroutinePool &&) = delete;
        CoroutinePool & operator=(const CoroutinePool &) = delete;
        CoroutinePool & operator=(CoroutinePool &&) = delete;

    private:
        static void LoopWork(CoroutinePool &oPool);

    private:
        bool m_bStarted;
        uint32_t m_uThreadCnt;
        uint32_t m_uRoutineCnt;
        ArraySyncQueue<CoroutineTaskCtx> m_queueJob;
        std::vector<std::shared_ptr<std::thread>> m_vecThread;
        std::mutex m_oMutex;
        std::condition_variable m_oCondition;
    };
    // class CoroutinePool end

    // class Future start
    class Future {
    public:
        /**
        * @brief 阻塞获得结果
        * 
        * @param uTimeoutMs 超时时间
        * @return true:成功, false:超时
        */
        bool Get(uint32_t uTimeoutMs = -1);

        /**
        * @brief 设置状态为完成
        */
        void SetFinished();

        Future();

        Future(const Future&) = delete;
        Future(Future&&) = delete;

        Future & operator=(const Future&) = delete;
        Future & operator=(Future&&) = delete;

    private:
        std::mutex m_oMutex;
        std::condition_variable m_oCondition;
        bool m_bFinished;
    };
    // class Future end
}

coroutine.cpp

/**
 * @file coroutine.cpp
 * @author souma
 * @brief 协程池的具体实现
 * @version 0.1
 * @date 2023-06-06
 * 
 * @copyright Copyright (c) 2023
 * 
 */

#include "coroutine.h"
#include <cstring>

using namespace std;
namespace comm {

    // class Coroutine start
    Coroutine::Coroutine() {
        m_pTaskCtx = nullptr;
    }

    void Coroutine::Register() {
        m_pTaskCtx = make_shared<CoroutineTaskCtx>();
        m_pTaskCtx->m_userFunc = [](){};
        m_pTaskCtx->m_pFuture = nullptr;
        SaveReg();
    }

    void Coroutine::Register(shared_ptr<CoroutineTaskCtx> pTaskCtx) {
        m_pTaskCtx = pTaskCtx;
        SaveReg();
    }

    inline void Coroutine::Yield() {
        Coroutine::Switch(this, Coroutine::GetCtx().m_pMainCoroutine.get());
    }

    bool Coroutine::CoYield() {
        if (GetCtx().m_vecCoroutine.size() == 0) {
            return false;
        }
        GetCtx().m_vecCoroutine[GetCtx().m_uCursor]->Yield();
        return true;
    }

    CoroutinePoolCtx & Coroutine::GetCtx() {
        thread_local CoroutinePoolCtx coroutinePoolCtx;
        return coroutinePoolCtx;
    }

    void Coroutine::MoveCursor() {
        GetCtx().m_uCursor = GetCtx().m_uCursor == GetCtx().m_vecCoroutine.size() - 1 ? 0 : GetCtx().m_uCursor + 1;
    }

    extern "C" __attribute__((noinline, weak))
    void Coroutine::Switch(Coroutine *pPrev, Coroutine *pNext) {
        // 1.保存pPrev协程的上下文, rdi和pPrev同指向
        // 2.加载pNext协程的上下文, rsi和pNext同指向
        asm volatile(R"(
            movq %rsp, %rax
            movq %rbp, 104(%rdi)
            movq %rax, 96(%rdi)
            movq %rbx, 88(%rdi)
            movq %rcx, 80(%rdi)
            movq %rdx, 72(%rdi)
            movq 0(%rax), %rax
            movq %rax, 64(%rdi)
            movq %rsi, 56(%rdi)
            movq %rdi, 48(%rdi)
            movq %r8, 40(%rdi)
            movq %r9, 32(%rdi)
            movq %r12, 24(%rdi)
            movq %r13, 16(%rdi)
            movq %r14, 8(%rdi)
            movq %r15, (%rdi)

            movq (%rsi), %r15
            movq 8(%rsi), %r14
            movq 16(%rsi), %r13
            movq 24(%rsi), %r12
            movq 32(%rsi), %r9
            movq 40(%rsi), %r8
            movq 48(%rsi), %rdi
            movq 64(%rsi), %rax
            movq 72(%rsi), %rdx
            movq 80(%rsi), %rcx
            movq 88(%rsi), %rbx
            movq 96(%rsi), %rsp
            movq 104(%rsi), %rbp
            movq 56(%rsi), %rsi
            movq %rax, (%rsp)
            xorq %rax, %rax
        )");
    }

    void Coroutine::DoWork(Coroutine *pThis) {
        pThis->m_pTaskCtx->m_userFunc();
        pThis->m_pTaskCtx->m_pFuture->SetFinished();
        pThis->m_pTaskCtx.reset();
        Coroutine::GetCtx().m_uWorkCnt--;
        pThis->Yield();
    }

    void* Coroutine::GetRsp() {
        // m_pRegister和m_pStack中间预留一个指针空间
        auto sp = std::end(m_pStack) - sizeof(void*);
        // 预定Rsp的地址保证能够整除8字节
        sp = decltype(sp)(reinterpret_cast<size_t>(sp) & (~0xF));
        return sp;
    }

    void Coroutine::SaveReg() {
        void *pStack = GetRsp();
        memset(m_pRegister, 0, sizeof m_pRegister);
        void **pRax = (void**)pStack;
        *pRax = (void*) DoWork;
        // rsp
        m_pRegister[12] = pStack;
        // rax
        m_pRegister[8] = *pRax;
        // rdi
        m_pRegister[6] = this;
    }
    // class Coroutine end

    // class CoroutinePool start
    CoroutinePool::CoroutinePool(uint32_t uThreadCnt, uint32_t uCoroutineCnt, uint32_t uJobQueueSize) : m_queueJob(uJobQueueSize) {
        m_bStarted = false;
        m_uThreadCnt = max(uThreadCnt, 1u);
        m_uRoutineCnt = max(uCoroutineCnt, 1u);
    }

    bool CoroutinePool::Run() {
        if (!__sync_bool_compare_and_swap(&m_bStarted, false, true)) {
            return false;
        }
        
        for (decltype(m_uThreadCnt) i = 0; i < m_uThreadCnt; ++i) {
            m_vecThread.emplace_back(make_shared<thread>(CoroutinePool::LoopWork, ref(*this)));   
        }
        return true;
    }

    void CoroutinePool::Stop() {
        if (!__sync_bool_compare_and_swap(&m_bStarted, true, false)) {
            return;
        }
        
        m_oCondition.notify_all();
        for (auto it = m_vecThread.begin(); it != m_vecThread.end(); ++it) {
            (*it)->join();
        }
        m_vecThread.clear();
    }

    shared_ptr<Future> CoroutinePool::Submit(const function<void()> &userFunc) {
        shared_ptr<Future> pNewFuture = make_shared<Future>();
        CoroutineTaskCtx *pTaskCtx = new CoroutineTaskCtx;
        pTaskCtx->m_pFuture = pNewFuture;
        pTaskCtx->m_userFunc = userFunc;

        if (!m_queueJob.Push(pTaskCtx)) {
            delete pTaskCtx, pTaskCtx = nullptr;
            return nullptr;
        }
        m_oCondition.notify_all();
        return pNewFuture;
    }

    CoroutinePool::~CoroutinePool() {
        Stop();
    }

    void CoroutinePool::LoopWork(CoroutinePool &oPool) {
        Coroutine::GetCtx().m_uCursor = 0;
        Coroutine::GetCtx().m_uWorkCnt = 0;
        Coroutine::GetCtx().m_pMainCoroutine = shared_ptr<Coroutine>(new Coroutine);
        Coroutine::GetCtx().m_pMainCoroutine->Register();

        Coroutine::GetCtx().m_vecCoroutine.clear();
        for (decltype(oPool.m_uRoutineCnt) i = 0; i < oPool.m_uRoutineCnt; ++i) {
            Coroutine::GetCtx().m_vecCoroutine.emplace_back(shared_ptr<Coroutine>(new Coroutine));
        }

        Coroutine *pMainCoroutine, *pCurCoroutine;
        while (oPool.m_bStarted || Coroutine::GetCtx().m_uWorkCnt > 0 || !oPool.m_queueJob.IsEmpty()) {
            
            pMainCoroutine = Coroutine::GetCtx().m_pMainCoroutine.get();
            pCurCoroutine = Coroutine::GetCtx().m_vecCoroutine[Coroutine::GetCtx().m_uCursor].get();
            
            if (pCurCoroutine->HasTask()) {
                Coroutine::Switch(pMainCoroutine, pCurCoroutine);
                Coroutine::MoveCursor();
                continue;
            }

            CoroutineTaskCtx *pTaskCtx = oPool.m_queueJob.Pop();
            if (pTaskCtx == nullptr) {
                if (Coroutine::GetCtx().m_uWorkCnt > 0) {
                    Coroutine::MoveCursor();
                    continue;
                }
                unique_lock<mutex> oLock(oPool.m_oMutex);
                oPool.m_oCondition.wait(oLock);
                continue;
            }

            pCurCoroutine->Register(shared_ptr<CoroutineTaskCtx>(pTaskCtx));
            ++Coroutine::GetCtx().m_uWorkCnt;
            Coroutine::Switch(pMainCoroutine, pCurCoroutine);
            Coroutine::MoveCursor();
        }
    }
    // class CoroutinePool end

    // class Future start
    Future::Future() {
        m_bFinished = false;
    }

    bool Future::Get(uint32_t uTimeoutMs) {
        unique_lock<mutex> oLock(m_oMutex);
        if (m_bFinished) {
            return true;
        }
        return m_oCondition.wait_for(oLock, chrono::milliseconds(uTimeoutMs)) == cv_status::no_timeout;
    }

    void Future::SetFinished() {
        {
            unique_lock<mutex> oLock(m_oMutex);
            m_bFinished = true;
        }
        m_oCondition.notify_all();
    }
    // class Future end

    // class ArraySyncQueue start
    template <class T>
    ArraySyncQueue<T>::ArraySyncQueue(uint32_t uCapacity, uint32_t uSleepUs, uint32_t uRetryTimes) {
        for (uint32_t i = 0; i < std::max(uCapacity, 1u); ++i) {
            m_vecQueue.emplace_back(nullptr);
        }
        m_uSleepUs = uSleepUs;
        m_uRetryTimes = uRetryTimes;
    }

    template <class T>
    bool ArraySyncQueue<T>::Push(T *pObj) {
        if (pObj == nullptr) {
            return false;
        }
        uint32_t uRetryTimes = 0;
        while (uRetryTimes <= m_uRetryTimes) {
            uint32_t uPushCursor = m_uPushCursor;
            if (uPushCursor == m_uPopCursor - 1 || (m_uPopCursor == 0 && uPushCursor == m_vecQueue.size() - 1)) {
                // 队列满了
                return false;
            }

            if (!__sync_bool_compare_and_swap(&m_vecQueue[uPushCursor], nullptr, pObj)) {
                uRetryTimes++;
                usleep(m_uSleepUs);
                continue;
            }

            m_uPushCursor = GetNextCursor(uPushCursor);
            return true;
        }
        // 竞争失败
        return false;
    }

    template <class T>
    T* ArraySyncQueue<T>::Pop() {
        uint32_t uRetryTimes = 0;
        while (uRetryTimes <= m_uRetryTimes) {
            uint32_t uPopCursor = m_uPopCursor;
            if (uPopCursor == m_uPushCursor) {
                return nullptr;
            }

            T* pToReturn = m_vecQueue[uPopCursor];
            if (pToReturn == nullptr || !__sync_bool_compare_and_swap(&m_vecQueue[uPopCursor], pToReturn, nullptr)) {
                usleep(m_uSleepUs);
                uRetryTimes++;
                continue;
            }
            m_uPopCursor = GetNextCursor(uPopCursor);
            return pToReturn;
        }
        return nullptr;
    }

    template <class T>
    uint32_t ArraySyncQueue<T>::GetNextCursor(uint32_t uCursor) {
        if (uCursor == m_vecQueue.size() - 1) {
            return 0;
        }
        return uCursor + 1;
    }

    template <class T>
    ArraySyncQueue<T>::~ArraySyncQueue() {
        m_uRetryTimes = -1;
        do {
            T *pObj = Pop();
            if (pObj == nullptr) {
                return;
            }
            delete pObj, pObj = nullptr;
        } while (true);
    }
    // class ArraySyncQueue end
}

8. 补充说明

8.1. 为什么不能-O0编译?

在-O0的情况下,编译器会给函数(coroutine.cpp:57)Coroutine::Switch包一层汇编指令,导致实际执行汇编指令不是期望的。具体可以分别用-O0和-O3在GDB下disassemble看到差异。

8.2. 如果函数使用栈很大怎么办?

源码中定义的协程栈为CO_STACK_SIZE=4096 + 65535KB,若用户函数使用的栈超过该范围会产生coredump。简单可行的解法是:1.尽量使用堆变量;2.改大CO_STACK_SIZE。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1092661.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

算法村开篇

大家好我是苏麟从今天开始我将带来算法的一些习题和心得体会等等...... 算法村介绍 我们一步步地学习算法本专栏会以闯关的方式来学习算法 循序渐进地系统的学习算法并掌握大部分面试知识 , 期待和大家一起进步 . 索大祝大家学有所成 , 前程似锦.

PyCharm运行Nosetests并导出测试报告

1. Pycharm运行Nosetests PyCharm可以使用两种方法&#xff0c;运行Nosetests测试文件&#xff1a; 1) 图形用户界面GUI a) 在PyCharm中&#xff0c;选中测试文件&#xff0c;如Tests/test_demo.py b) 鼠标右键选择Run Nosetests in test_demo.py即可执行测试 注1&#xff…

【大数据Hive】hive select 语法使用详解

目录 一、前言 二、Hive select 完整语法树 三、Hive select 操作演示 3.1 数据准备 3.1.1 创建一张表 3.1.2 将数据load加载到t_usa_covid19表 3.1.3 再创建一张分区表 3.1.4 使用动态分区插入数据 3.2 select 常用语法 3.2.1 查询所有字段或者指定字段 3.2.2 查询…

【数据库系统概论】第七章数据库设计

7.1数据库设计概述 数据库设计定义是什么&#xff1f; 数据库设计(database design)&#xff1a;数据库设计是指对于一个给定的应用环境&#xff0c;构造(设计)优化的数据库逻辑模式和物理结构&#xff0c;并据此建立数据库及其应用系统&#xff0c;使之能够有效地存储和管理…

【排序算法】详解冒泡排序及其多种优化稳定性分析

文章目录 算法原理细节分析优化1优化2算法复杂度分析稳定性分析总结 算法原理 冒泡排序(Bubble Sort) 就是从序列中的第一个元素开始&#xff0c;依次对相邻的两个元素进行比较&#xff0c;如果前一个元素大于后一个元素则交换它们的位置。如果前一个元素小于或等于后一个元素…

RootSIFT---SIFT图像特征的扩展

RootSIFT是论文 Three things everyone should know to improve object retrieval - 2012所提出的 A Comparative Analysis of RootSIFT and SIFT Methods for Drowsy Features Extraction - 2020 当比较直方图时&#xff0c;使用欧氏距离通常比卡方距离或Hellinger核时的性能…

分析智能平台VMware Greenplum 7 正式发布!

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

芯片学习记录TLP104

TLP104 芯片介绍 引脚信息 引脚1阳极3阴极4GNDGND5输出6VCC5V&#xff08;4.5~30&#xff09; 推荐使用条件 *此项目表示操作范围&#xff0c;而不是建议的操作条件。 注&#xff1a;建议的操作条件作为设计指南&#xff0c;以获得预期的性能设备。此外&#xff0c;每个项目…

HTML5播放 M3U8的hls流地址

在HTML5页面上播放M3U8的hls流地址 <!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title>视频播放</title> <script src"https://cdn.jsdelivr.net/npm/hls.jslatest"></script> &…

Spring6注解管理Bean

文章目录 过程搭建子模块spring6-ioc-annotation使用注解定义 BeanAutowired注入①场景一&#xff1a;属性注入②场景二&#xff1a;set注入③场景三&#xff1a;构造方法注入④场景四&#xff1a;形参上注入⑤场景五&#xff1a;只有一个构造函数&#xff0c;无注解⑥场景六 A…

Vue3 + Quasar系列-代码配置以及报错汇总记录(不断更新中)

1. Vue3 Quasar系列-代码配置打包去掉hash后缀 去掉hash https://quasar.dev/quasar-cli-vite/developing-pwa/configuring-pwa 2. Vue3 Quasar改变主题背景 quasar的样式和其他的框架修改不太一样&#xff0c;需要我们使用动态的方式来进行变更&#xff0c;一般来说有两…

计算机毕业设计 高校实习信息发布网站的设计与实现 Javaweb项目 Java实战项目 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

宝塔使用腾讯COS存储实现自动备份服务器网站数据图文教程

一、进入宝塔安装腾讯COS 点击设置打开后需要配置以下cos参数 二、腾讯云创建COS存储桶 选择私有读写&#xff0c;其他默认就行 三、创建访问密钥 四、配置宝塔中腾讯COS相关设置 很多人是配置错误导致无法正常链接cos region为cos存储桶所属地域 Bucker为存储桶名称 五、…

力扣-461.汉明距离

Method 1 直接比较x&#xff0c;y二进制中的每一位&#xff0c;如果不同则cnt加一&#xff0c;并且x&#xff0c;y每次右移一位 class Solution { public:int hammingDistance(int x, int y) {int cnt 0;while(x > 0 && y > 0) {if((x & 1) ! (y & 1)…

MySQL 2023 MySQL Summit 大会感受,我们距离MySQL 新技术越来越远

开头还是介绍一下群&#xff0c;如果感兴趣PolarDB ,MongoDB ,MySQL ,PostgreSQL ,Redis, Oceanbase, Sql Server等有问题&#xff0c;有需求都可以加群&#xff0c;群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;…

2023.10.14 培训总结

培训内容 数字模型联合仿真及集成测试技术 MBSE(Model-Based-System-Engiaeering&#xff09; 参数化建模参数化仿真 产生的疑问 支持面向对象支持CAE CFD工具优化工具 飞机的业务功能 开发分布式架构 新技术 WSDL协议DDS 发布/订阅SAOPCORBA 明显开发者 Chris Garrett 美…

c# xml 参数配置表的使用

使用简介 实际使用界面 配置表管理界面 进入 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms;…

挖机技术哪家强

挖机技术哪家强&#xff0c;中国山东找蓝翔&#xff0c;开挖机是我曾经的梦想&#xff0c;每个男人心中都有一台自己的挖机&#xff0c;近半年做的项目就是关于挖机销售CRM&ERP系统&#xff0c; 今天我们聊聊关于挖机的基本知识。 注&#xff1a;此文并非广告&#xff0c;…

Lua 协程

一、协程 Lua 中使用半协程的方式进行组织代码。 和线程的最大区别在于&#xff0c;一个多线程程序可以并行运行多个线程&#xff0c;而协程却需要彼此协作运行&#xff0c;即任意指定时刻只能一个协程运行&#xff0c;且只有当正在运行的协程显式地要求被挂起时&#xff0c;…

Net6 用imagesharp 实现跨平台图片处理并存入oss

项目要求&#xff1a;生成电子证书 一、模板文件在OSS中&#xff0c;直接加载 二、向模板文件添加二维码 三、向模板文件添加多行文字 四、生成二维码&#xff0c;存入本地&#xff0c; 五、向模板文件添加二维码 代码实现步骤 一、建立.net 6 API项目&#xff0c;安装N…