协程(coroutine)的原理与实现

C++20中的协程(Coroutine)功能解析:co_await C++20 协程的co_await实现 阅读详情
AI摘要

本文详细介绍了协程的概念,将其与线程和回调进行对比,探讨了协程调度原理、上下文切换、使用场景及C/C++实现方式,重点讲解了C语言中的ucontext系列函数在协程中的应用实例。

协程概念

协程相当于用户态的线程,更轻量级。线程的调度是操作系统完成的,而协程的调度是由用户态控制。线程相比进程来说,上下文切换快,资源开销变小,但是作为操作系统的,但是多线程如果设计的不够好,可能有大量的锁同步、切换等待。除此之外,如果一个系统的瓶颈在 IO 上,一个线程可能不能完全发挥出它的作用。而协程一般在线程中运行,有用户态的调度器调度,不需要进行反复的系统调用,而且可以利用到线程的并发优势,协程的执行效率极高,在一些高并发系统应用比较广泛。

协程可以看作一种使用起来比较特殊的函数,函数的调用一般是有一个入口和返回,且调用顺序明确。但是协程在函数内部可以让出(yield),转而执行另一个函数,但是此时该协程并未真正结束,只是暂时让出 CPU 执行权,在适当的时候返回来可以接着恢复执行(resume),这种执行的转换不是函数调用,而是类似于 CPU 的中断。所以协程也成为轻量级线程。

默认的协程切换的顺序可能是没有保障的,比如执行 A, B, C 三个子程序,因为第一个子程序遇到 IO 转而切换到 C 进行处理,这时候,子程序的执行可能变成了 A, C, B,但也有一些解决办法。

协程并不是万能的,合适的场景使用合适的工具更重要。协程可以减少 Callback 的使用,但不能完全替代 Callback。如果基于事件驱动的编程就不能发挥协程的作用而用 Callback 更合适。

但是协程是协作式多任务的,而线程典型是抢占式多任务的。这意味着协程提供并发性而非并行性。单个线程中的协程并不是并行执行的,而是顺序执行的,每次只有一个协程在被调度执行。只能是一个协程结束或 yield 后,再执行另一个协程。而线程才是真正并发执行的。协程只是以一种特殊的方式去运行一个函数,不要在协程内使用线程级别的锁来做协程同步,如果一个协程持有了锁而让出了执行权,其他协程再尝试去加锁,这个整个线程将发生死锁,该线程的所有协程都无法执行了。

协程并不都是不需要锁,因为协程调度一般是 N-M 模型(N个线程,M个协程),支持协程在多线程中切换,也可以指定协程在固定的线程中执行,这样可以重复利用每一个线程。如果多个线程运行的协程访问共享资源还是有竞争的,需要手动加锁。

协程并不能完全替代线程,而多线程/进程+多协程才是最好的办法,即充分利用了 CPU 的多核性能,有充分发挥协程的高效率。

协程的创建,就是把一个函数包装成一个协程对象,再用协程的方式将这个函数跑起来,协程调度就使用协程调度器把这些协程一个个都消化掉。IO 调度其实就是在调度协程时,如果发现这个协程在等待 IO 就绪,先让出这个协程的执行权,等 IO 就绪后再恢复这个协程的运行。定时器,就是给调度协程预设一个协程对象,等定时时间到了就恢复协程对象。

现在已经实现协程的编程语言包括:

  • 重量级的有 C#、erlang、golang 等

  • 轻量级有 python、lua、javascript、ruby

  • 还有函数式的 scala、scheme等。

C/C++ 之前不直接支持协程语义(从 C++20 开始支持),但也有不少开源的协程库,如:

Protothreads:一个“蝇量级” C 语言协程库(利用 C 语言的 switch case 的奇淫技巧)。

libco:来自腾讯的开源协程库libco(利用汇编代码)Github。

coroutine:云风的一个C语言同步协程库(利用 glibc 的 ucontext 组件)Github。

C/C++ 语言其标准内并未未直接支持协程。但 Boost C++ Library 中的 Boost.Context 实现了在POSIX、Mac OS X 和 Windows 上支持 ARM、MIPS、PowerPC、SPAR 和 x86 的上下文切换。可以在 Boost.Context 之上建造协程。

C 语言标准库中有非局部跳转函数 setjmp 和 longjmp 分别是保存和恢复:栈指针、程序计数器、被调用者保存的寄存器和 ABI 要求的任何其他内部状态。在 C99 标准中,跳转到已经用 return 或 longjmp 终止的函数是未定义的,但是大多数 longjmp 实现在跳转时不专门销毁调用栈中的局部变量,在被后续的函数调用等覆写之前跳转回来恢复时仍是原样,这允许在实现协程时谨慎的用到它们。

POSIX.1-2001/SUSv3 进一步提供了操纵上下文的强力设施:setcontext、getcontext、makecontext和swapcontext,可方便地用来实现协程,但是由于 makecontext 的参数定义不符合 C99 标准要求,这些函数在 POSIX.1-2004 中被废弃,并在 POSIX.1-2008 中被删除。POSIX.1-2001/SUSv3定义了 sigaltstack,可用来在不能获得 makecontext 的情况下稍微迂回的实现协程。极简实现不采用有关的标准 API 函数进行上下文交换,而是写一小块内联汇编只对换栈指针和程序计数器故而速度明显的要更快。

现在也有很多作者开源了写成库,包括:libtask、lthread、libCoroutine、libconcurrency、libcoro、libdill、libaco、libco等等。

ucontext 系列函数

Linux 下可以使用 ucontext 系列函数实现协程,其声明如下:

#include <ucontext.h>

int getcontext(ucontext_t *ucp);
int setcontext(const ucontext_t *ucp);
void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);
int swapcontext(ucontext_t *oucp, const ucontext_t *ucp);

这些函数允许在用户级别的对一个进程中的多个控制线程之间进行上下文切换。

mcontext_t 类型与机器有关,是不透明的。 ucontext_t 类型是一个结构体,至少有以下字段:

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

sigset_t 和 stack_t 定义在 <signal.h> 中。

uc_link 指向当前上下文终止时将被恢复的上下文。

uc_sigmask 是这个上下文中被屏蔽的信号集。

uc_stack 是这个上下文使用的堆栈。

uc_mcontext 是保存的上下文的机器特定表示。

getcontext

int getcontext(ucontext_t *ucp);

函数 getcontext 将 ucp 指向的结构初始化为当前活动的上下文。将指向的结构体初始化为当前活动的上下文。

setcontext

int setcontext(const ucontext_t *ucp);

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

函数 setcontext 恢复 ucp 所指向的用户上下文。这个 ucp 应该是通过 getcontext 或者 makecontext 得到的。

如果上下文是通过调用 getcontext 获得的,程序执行继续执行。

如果上下文是通过调用 makecontext 获得的,那么程序的执行将通过 makecontext 的第二个参数指定的函数 func 来继续执行。当这个 func 函数返回,则恢复 makecontext 第一个参数指向的上下文 context_t 中指向的 uc_link。如果 uc_link 为 NULL,则线程退出。

一个简单的实例:

#include <stdio.h>
#include <unistd.h>
#include <ucontext.h>

int main()
{
    int a = 1;
    ucontext_t ctx_1;
    getcontext(&ctx_1); //将当前上下文保存到ctx_1中

    a++;
    printf("%d\n", a);

    setcontext(&ctx_1); //恢复ctx_1保存的上下文

    return 0;
}

这个函数的运行结果为:

1
2
3
...

makecontext

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

makecontext 函数修改了由 ucp 指向的上下文(该上下文是通过 getcontext 获得的)。调用者必须给这个上下文分配一个新的堆栈,然后给该上下文指定栈空间 ucp->stack,设置后继的上下文 ucp->uc_link。

当这个上下文后来被激活时(使用 setcontext 或 swapcontext),函数 func 被调用,并传递给 argc 之后的一系列整数参数。调用者必须在 argc 中指定参数的数量。这个函数返回时,继任的后继上下文被激活。如果后继上下文指针为 NULL,则线程退出。

一个简单的实例:

#include <stdio.h>
#include <unistd.h>
#include <ucontext.h>

void func(void *arg)
{
    puts("exec func.");
}

int main()
{
    char stack[1024];
    ucontext_t ctx_1;
    getcontext(&ctx_1); //将当前上下文保存到ctx_1中
    ctx_1.uc_stack.ss_sp = stack;
    ctx_1.uc_stack.ss_size = sizeof(stack);
    ctx_1.uc_stack.ss_flags = 0;
    ctx_1.uc_link = NULL;

    makecontext(&ctx_1, (void(*)(void))func, 0);
    
    setcontext(&ctx_1); //激活ctx_1,先执行func函数,func返回时,执行后继上下文,因为ctx_1.uc_link为NULL,直接为空
    printf("main exit.");
    return 0;
}

运行结果:

exec func.

swapcontext

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

swapcontext 函数将当前上下文保存在 oucp 所指向的结构体中,然后激活 ucp 所指向的上下文。因为一个线程必须可以主动让出 CPU 给其他线程,swapcontext 函数就可以完成这个任务。

一个简单的实例:

#include <iostream>
#include <ucontext.h>

void func(void *arg)
{
    puts("exec func.");
}

int main()
{
    char stack[1024];
    ucontext_t ctx_1;
    ucontext_t ctx_2;
    getcontext(&ctx_1); //将当前上下文保存到ctx_1中
    ctx_1.uc_stack.ss_sp = stack;
    ctx_1.uc_stack.ss_size = sizeof(stack);
    ctx_1.uc_stack.ss_flags = 0;
    ctx_1.uc_link = &ctx_2;

    makecontext(&ctx_1, (void(*)(void))func, 0);
    //swapcontext将当前上下保存在ctx_2中,再激活ctx_1的上下文
    //而上面ctx_1使用了makecontext,在被激活时,是先执行func函数,当func函数返回时,执行ctx_1.uc_link指向的后继上下文
    //因为后继上下文ctx_2是在swapcontext中获取的,所以将会从swapcontext后面继续执行
    swapcontext(&ctx_2, &ctx_1);
    puts("main exit.");
    return 0;
}

运行结果:

exec func.
main exit.

如果将上面的 ctx_1.uc_link = &ctx_2; 改为 ctx_1.uc_link = NULL; 执行结果将变为:

exec func.

使用ucontext系列函数实现协程

此代码是从 sylar 框架中取出并简化的一个 C++ 封装的协程,使用了 ucontext 系列函数实现,仅仅是封装了协程对象,不包含协程调度模块以及 hook 模块,有兴趣的同学可以参考 sylar 源代码。

//yCoroutine.h

#ifndef __YCOROUTINE_H__
#define __YCOROUTINE_H__

#include <memory>
#include <functional>
#include <ucontext.h>

class yCoroutine : public std::enable_shared_from_this<yCoroutine>
{
public:
    typedef std::shared_ptr<yCoroutine> ptr;
    
    enum State
    {
        READY, //就绪状态
        RUNNING, //运行状态
        TERM,    //结束状态
    };
private:
    yCoroutine();

public:
    yCoroutine(std::function<void()> cb, size_t stacksize = 0, bool run_in_scheduler = false);
    ~yCoroutine();
    
    void reset(std::function<void()> cb);
    //当前协程恢复到执行状态
    void resume();
    //当前协程让出执行权
    void yield();
    //获取协程ID
    uint64_t getId() const { return m_id; }
    //获取协程的状态
    State getState() const { return m_state; }

public:
    //设置当前正在运行的协程,即设置线程局部变量t_coroutine的值
    static void SetThis(yCoroutine* co);
    //返回当前线程正在执行的协程,如果未创建,返回该线程创建的第一个协程
    static yCoroutine::ptr GetThis();
    //协程入口函数
    static void MainFunc();
    //获取当前协程ID
    static uint64_t GetCoroutineId();


private:
    uint64_t m_id = 0;          //协程id
    uint32_t m_stacksize = 0;   //协程栈空间大小
    State m_state = READY;      //协程状态
    ucontext_t m_ctx;           //协程上下文
    void* m_stack = nullptr;    //协程栈地址
    std::function<void()> m_cb; //协程入口函数
    bool m_runInScheduler;      //本携程是否参与调度器调度
};

#endif /*__YCOROUTINE_H__*/
//yCoroutine.cpp

#include "yCoroutine.h"
#include <iostream>
#include <atomic>
#include <cassert>
#include <thread>

static std::atomic<uint64_t> s_coroutine_id {0};
static std::atomic<uint64_t> s_coroutine_count{0};
static uint32_t g_co_stack_size = 128 * 1024;

static thread_local yCoroutine* t_coroutine = nullptr;              //线程当前运行的协程
static thread_local yCoroutine::ptr t_thread_coroutine = nullptr;   //线程的主协程

yCoroutine::yCoroutine()
{
    SetThis(this);
    m_state = RUNNING;
    getcontext(&m_ctx);
    ++s_coroutine_count;
    m_id = s_coroutine_id++;
}

yCoroutine::~yCoroutine()
{
    std::cout << "yCoroutine::~yCoroutine id = " << m_id << std::endl;
    --s_coroutine_count;
    if (m_stack)
    {
        assert(m_state == TERM);
        free(m_stack);
    }
    else
    {
        //没有栈说明就是当前线程的主协程
        assert(!m_cb);
        assert(m_state == RUNNING);

        yCoroutine* cur_co = t_coroutine; 
        if (cur_co == this)
            SetThis(nullptr);
    }
}

void yCoroutine::SetThis(yCoroutine* co)
{
    if (!co) return;
    t_coroutine = co;
}

yCoroutine::ptr yCoroutine::GetThis()
{
    if (t_coroutine)
        return t_coroutine->shared_from_this();
    //这里会调用私有构造函数,然后调用SetThis,将t_coroutine设置为this
    yCoroutine::ptr main_co(new yCoroutine);
    assert(t_coroutine == main_co.get());
    t_thread_coroutine = main_co;
    return t_coroutine->shared_from_this();
}

//带参构造函数用于创建其他协程,需要分配栈空间
yCoroutine::yCoroutine(std::function<void()> cb, size_t stacksize, bool run_in_scheduler)
    : m_id(s_coroutine_id++), m_cb(cb), m_runInScheduler(run_in_scheduler)
{
    ++s_coroutine_count;
    m_stacksize = stacksize ? stacksize : g_co_stack_size;
    m_stack = malloc(m_stacksize);
    getcontext(&m_ctx);
    m_ctx.uc_link = nullptr;
    m_ctx.uc_stack.ss_sp = m_stack;
    m_ctx.uc_stack.ss_size = m_stacksize;

    makecontext(&m_ctx, &yCoroutine::MainFunc, 0);
    std::cout << "yCoroutine::yCoroutine() id=" << m_id << std::endl;
}

void yCoroutine::reset(std::function<void()> cb)
{
    assert(m_stack);
    assert(m_state == TERM);
    m_cb = cb;
    getcontext(&m_ctx);
    m_ctx.uc_link = nullptr;
    m_ctx.uc_stack.ss_sp = m_stack;
    m_ctx.uc_stack.ss_size = m_stacksize;
    makecontext(&m_ctx, &yCoroutine::MainFunc, 0);
    m_state = READY;
}

void yCoroutine::yield()
{
    //协程运行完成后会自动yield一次,然后回到主协程
    assert(m_state == RUNNING || m_state == TERM);
    SetThis(t_thread_coroutine.get());
    if (m_state != TERM)
        m_state = READY;
    //如果协程不参与调度器,与当前线程的主协程进行swap
    if(!m_runInScheduler)
        swapcontext(&m_ctx, &(t_thread_coroutine->m_ctx));
    // //如果协程参与调度器调度,应该和调度器的主协程进行swap
    // else
    //     swapcontext(&m_ctx, &(Scheduler::GetMainCoroutine()->m_ctx));
}

void yCoroutine::resume()
{
    assert(m_state != TERM && m_state != RUNNING);
    SetThis(this);  //设置为线程当前运行的协程
    m_state = RUNNING;
    //如果协程不参与调度器,与当前线程的主协程进行swap
    if(!m_runInScheduler)
        swapcontext(&(t_thread_coroutine->m_ctx), &m_ctx);
    // //如果协程参与调度器调度,应该和调度器的主协程进行swap
    // else
    //     swapcontext(&(Scheduler::GetMainCoroutine()->m_ctx), &m_ctx));

}

void yCoroutine::MainFunc()
{
    yCoroutine::ptr cur_co = GetThis(); //t_coroutine引用计数+1
    assert(cur_co);
    cur_co->m_cb(); //执行回调函数
    cur_co->m_cb = nullptr;
    cur_co->m_state = TERM;
    auto raw_ptr = cur_co.get();
    cur_co.reset();    //手动让t_coroutine引用计数-1
    raw_ptr->yield();
}

uint64_t yCoroutine::GetCoroutineId()
{
    if (t_coroutine)
        return t_coroutine->getId();
    return 0;
}
// main test
void test_coroutine_func()
{
    std::cout << "test_coroutine_func begin." << std::endl;
    std::cout << "before test_coroutine_func yield." << std::endl;
    std::cout << "cur croutine id :" << yCoroutine::GetCoroutineId() << std::endl;
    yCoroutine::GetThis()->yield();
    std::cout << "after test_coroutine_func yield." << std::endl;
    //当前coroutine结束自动返回主协程
    std::cout << "test_coroutine_func end." << std::endl;
}

void test_thread_func()
{
    std::cout << "thread_func begin." << std::endl;
    //初始化当前线程主协程
    yCoroutine::GetThis();
    //在线程中创建一个新协程
    yCoroutine::ptr co(new yCoroutine(test_coroutine_func, 0, false));
    std::cout << "cur croutine id :" << yCoroutine::GetCoroutineId() << std::endl;
    std::cout << "before test_thread_func resume1." << std::endl;
    co->resume();
    std::cout << "after test_thread_func resume1." << std::endl;

    std::cout << "before test_thread_func resume2." << std::endl;
    co->resume();
    std::cout << "after test_thread_func resume2." << std::endl;

    std::cout << "thread_func end." << std::endl;
}

int main()
{
    std::thread th1(test_thread_func);
    th1.join();
    return 0;
}

运行结果:

thread_func begin.
yCoroutine::yCoroutine() id=1
cur croutine id :0
before test_thread_func resume1.
test_coroutine_func begin.
before test_coroutine_func yield.
cur croutine id :1
after test_thread_func resume1.
before test_thread_func resume2.
after test_coroutine_func yield.
test_coroutine_func end.
after test_thread_func resume2.
thread_func end.
yCoroutine::~yCoroutine id = 1
yCoroutine::~yCoroutine id = 0

参考:
https://github.com/sylar-yin/sylar
https://zh.wikipedia.org/wiki/%E5%8D%8F%E7%A8%8B
https://www.zhihu.com/question/20511233/answer/83307265
https://blog.youkuaiyun.com/qq910894904/article/details/41911175

c++20协程基础概念 c++协程 阅读详情

相关推荐

【C++】什么是协程?深入理解协程的实现原理与应用

一个进程就是一个程序的运行实例。它有自己的地址空间,除了通过共享内存,不与其他进程共享。线程存在于进程中,它们不能脱离进程存在,尽管在Linux中,进程和线程都被视为任务。它们以相同的方式被调度,并且在内核级别有相同的控制结构。尽管如此,线程被认为是轻量级的,因为程序的初始负载的较大开销由父进程承担。但这并不是完整的情况。还有纤程和协程。如果说进程和线程是真正的并发并且在共享资源上并行工作,纤程就像线程,但不符合并发。虽然线程通常依赖于任务调度器的抢占式时间分片,纤程使用协作式多任务处理。

2401_86353562的博客 1218

协程的原理

@[TOC] 协程

Birate的博客 9484

unity update 协程_unity3d中协程Coroutine的的原理及使用

我们学习下unity3d中协程Coroutine的的原理及使用1.什么是协调程序unity协程是一个能暂停执行,暂停后立即返回,直到中断指令完成后继续执行的函数。它类似一个子线程单独出来处理一些问题,性能开销较小,但是他在一个MonoBehaviour提供的主线程里只能有一个处于运行状态的协程。2.协同程序的特点1、协程在中断指令(YieldInstruction)产生时暂停执行2、协程一暂停执行...

weixin_32384503的博客 745

什么是协程(一次性讲清)

正如网络上大家都在讲的“用户态的线程,更轻量级”,

qq_62821433的博客 5786

C++之协程Coroutine

C++20协程是一种轻量级的无栈协程,允许函数执行过程中主动挂起和恢复,相比线程切换开销极低。协程适用于异步IO、生成器、状态机等场景,相比线程具有内存占用小、切换高效的特点。其实现采用"语言机制+库扩展"架构,通过编译器生成底层控制流代码,开发者自定义Promise和Awaitable来控制具体行为。

飞鹰技术 230

Kotlin:深度理解协程挂起恢复实现原理。纯源码分析。,张口就来

) {})//执行结果09:05:46:961 [thread] suspendFunction----执行之后协程框架的launch等等对上述的封装,使用起来更加顺手而已。从执行的结果来看,先输出了coroutine start,之后挂起两秒钟,继续执行之后的逻辑。那我们就用这个demo来分析,协程的挂起与恢复是如何实现的。😮挂起函数分析👀首先我们需要分析一下挂起函数是个什么玩意儿~😮将上述挂起函数代码转换成java看看。2。

2401_84123232的博客 1203

协程的原理与实现:qemu 之 Coroutine

一,概念 在操作系统(os)级别,有进程(process)和线程(thread)两个我们看不到但又实际存在的“东西”,这两个东西都是用来模拟“并行”的,并且在OS内做为调度的实体单元,各自拥有独立的CPU资源。 Coroutine: 翻译成”协程“, 但它实际上并不是一个可由OS调度的实体,而是可以理解为“由用户层自己调度执行的一段代码片段”。 二,进程,线程,协程 Process: 进程。 进程...

cgm88s的专栏 1625

科研绘图实战:用Adobe Illustrator高效排版期刊论文图片

本文详细介绍了使用Adobe Illustrator高效排版科研论文图片的完整工作流。针对期刊投稿常见的图片格式问题,文章重点讲解了矢量图与位图的预处理、AI中的精准对齐与分布技巧、文字样式统一管理以及最终输出TIFF/PDF等符合出版要求的格式。掌握这套方法能确保图片满足高分辨率、规范字体和精确尺寸等期刊硬性标准,显著提升投稿效率。

tomato的博客 1433

2.3.1 协程设计原理与汇编实现coroutine

client端:不只是dns请求,http请求、redis数据请求,mysql请求,mongodb请求的同步异步类似server端:进程同一个,但是server的网页里的一堆小按钮子请求,肯定做成异步,不然太卡了, server 做到消息队列,push到任务再执行,也是一种异步,比同步快,非串行上面是同步,适合人类思考,下面是异步快,任务检测io,callback通知处理读写,把任务压到线程池。

weixin_44925529的博客 1059

Tomcat漏洞——弱口令爆破

靶机:CentOS7 IP:192.168.100.40。攻击机:kali IP:192.168.100.60。版 权 声 明:本博客所有文章除特别声明外,均采用。文章版权归作者所有,未经允许请勿转载!作 者:PeiyuJue。

weixin_57379923的博客 909

从数据手册到代码:一步步解读SC7A20寄存器配置与加速度值换算原理

本文详细解析了SC7A20三轴加速度传感器的寄存器配置与加速度值换算原理。从数据手册出发,逐步拆解关键寄存器设置,并推导原始ADC值到物理加速度的完整换算过程,帮助开发者深入理解传感器底层工作原理,提升嵌入式开发效率。

weixin_29179311的博客 368

2.3.1 协程设计原理与汇编实现coroutine 2

然后再封装switch,到send recv里面检查可不可行,可以switch,async就是在send recv前检查可不可以send recv。setjump(1)这里ret=1, func(1), arg2 longjump2。栈大小取决于业务量 ctx[0] ctx[1]互相存储!setjump返回ret=0, func(0) arg1 longjump1。context上下文, 入口func1,2,定义栈顶stack1。异步的实现,但是变成逻辑变成右上角同步的感觉,但实际还是异步!

weixin_44925529的博客 414

手把手教你用Kali Linux搭建vulntarget-a靶场环境(附详细配置步骤)

本文详细介绍了如何在Kali Linux环境下搭建vulntarget漏洞靶场,包括环境规划、虚拟化平台配置、靶机系统部署及安全工具链配置等关键步骤。通过实战演练,帮助网络安全从业者和初学者掌握专业级漏洞实验平台的搭建与管理技巧,提升安全测试能力。

weixin_29279101的博客 107

cloudwu/coroutine 源码分析

这个 C 协程库是云风(cloudwu) 写的,其接口风格与 Lua 协程类似,并且都是非对称 stackful 协程。这个是源代码中的示例: 这段代码输出: 与其等价的 Lua 代码为: 这段代码输出: 与其等价的 C++ 20 代码为: 这段代码输出 对比三段代码,可以看到 C 版本比 Lua 版本多了 coroutine_open 和 coroutine_close,C 版本需要保存一个全局状态 S。Lua 版本无法在创建协程的时候指定参数,必须在之后的 resume 把参数传

m0_62396648的博客 419

【数据库】银行/信贷业务测试工作中涉及的数据库表格

银行/信贷业务测试工作中涉及的数据库表格,金融信贷测试人必看!

孤之鹜的博客 1171

硬件工程师笔记——电子器件汇总大全

本文系统总结了14种常用电子器件的工作原理及功能应用,包括电阻、压敏电阻、电容、电感、二极管、三极管、MOS管、晶振、光耦、IGBT、继电器、磁珠、可控硅和线性稳压器。每种器件从结构原理、工作特性到典型应用进行全面解析,如电阻的限流分压、电容的储能滤波、三极管的放大开关等。重点阐述了各器件在电路设计中的关键作用,如IGBT的功率控制、继电器的隔离保护、LDO的电压稳定等,并提供了实际电路应用案例。文章还整理了硬件工程师笔试面试相关的知识点和真题资源,为电子工程师提供全面的技术参考。

XU157303764的博客 2万+

Unity协程原理与实现

转载自https://www.cnblogs.com/yespi/p/9847533.html 一、介绍 协程Coroutine在Unity中一直扮演者重要的角色。可以实现简单的计时器、将耗时的操作拆分成几个步骤分散在每一帧去运行等等,用起来很是方便。 但是,在使用的过程中有没有思考过协程是怎么实现的?为什么可以将一段代码分成几段在不同帧执行? 本篇文章将从实现原理上更深入的理解协程,最后肯定也要实现我们自己的协程。 关于协程的用法网上有很多介绍,不清楚的话可以看下官方文档,这里不做赘述。 二、迭代器 .

江陵野少的博客 784

STM32CubeMX之DAC产成正弦波

“在之前的文章中曾经介绍过用TIM产生PWM做DAC用,STM32定时器输出PWM做DAC。部分型号的STM32单片机内部有专用的DAC,本篇文章将介绍如何用内部DAC+DMA产生正弦波...

zhang062061的专栏 1513

python协程使用场景_详细解读tornado协程(coroutine)原理

tornado中的协程是如何工作的协程定义Coroutines are computer program components that generalize subroutines for nonpreemptive multitasking, by allowing multiple entry points for suspending and resuming execution at c...

weixin_39946996的博客 488

PCB仿真软件:Altium Designer_(6).PCB布线设计:自动布线与手工布线

通过以上步骤,我们完成了一个简单的PCB设计案例。在这个过程中,我们使用了自动布线和手工布线的综合策略,确保布线质量符合设计要求。创建PCB项目:在Altium Designer中创建一个新的PCB项目。导入原理图:将原理图文件导入到PCB项目中。设置布线规则:通过设置必要的布线规则,如线宽、间距等。启动自动布线:使用自动布线器进行初步布线。手工优化布线:通过手工布线工具优化关键信号的布线路径。保存和导出PCB文件:保存PCB文件并导出Gerber文件,用于生产。

kkchenjj的博客 2522

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Unity协程(Coroutine)原理深入剖析再续

Unity协程(Coroutine)原理深入剖析再续 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com 前面已经介绍过对协程(Coroutine)的认识和理解,主要讲到了Unity引擎在执行协程(Coroutine)的原理(Unity协程(Coroutine)原理深入剖析)和对协程(Corou...

weixin_30780221的博客 129

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

Unity协程(Coroutine)之yield和迭代原理分析

协程其实就是一个IEnumerator(迭代器),IEnumerator 接口有两个方法 Current 和 MoveNext() ,前面介绍的 TaskManager 就是利用者两个方法对协程进行了管理,只有当MoveNext()返回 true时才可以访问 Current,否则会报错。迭代器方法运行到 yield return 语句时,会返回一个expression表达式并保留当前在代码中的位置。 当下次调用迭代器函数时执行从该位置重新启动。

天生爱赞美的博客 8069

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

C# 的 async/await 其实是stackless coroutine

注: 最近Java 19引入的虚拟线程火热,还有很多人羡慕 go的 coroutine,很多同学一直有一个疑问: C# 有 虚拟线程或者 coroutine吗,下面的这个回答可以解决问题。这里节选的是知乎上的hez2010 的高赞回答:https://www.zhihu.com/question/554133167/answer/2690808608C# 的 async/await 其实就是一个...

dotNET跨平台 873

Lua实验室之Coroutine

前言 Lua做为一个小巧的可嵌入的语言,学习起来难度并不大,唯一一点需要花点时间掌握的就是Coroutine,本文就来说一下Coroutine相关的技术。 什么是Coroutine 协程从表面意思来看就是可以协同运行的函数,可以看成是一个用户态的线程,由客户自己调度,切换不需要陷入内核态,效率比较高。用户控制切换,自动保存上下文状态,切换之间可以通过参数通信,可用同步的方式实现异步。这是...

koo叔的博客 552

Unity协程(Coroutine)原理深入剖析

记得去年6月份刚开始实习的时候,当时要我写网络层的结构,用到了协程,当时有点懵,完全不知道Unity协程的执行机制是怎么样的,只是知道函数的返回值是IEnumerator类型,函数中使用yield return ,就可以通过StartCoroutine调用了。后来也是一直稀里糊涂地用,上网google些基本都是例子,很少能帮助深入理解Unity协程的原理的。         本文只是从Uni

yaokang522的专栏 1123

c coroutine

今天看了下云风写的关于 c coroutine博客(代码), 发现coroutine 实现原理其实还比较简单,就用户态栈切换,只需要几十行汇编,特别轻量级。 具体实现 1. 创建一个coroutine: 也就是创建一块连续内存,用于存放栈空间,并设置好入口函数所需要的寄存器  makecontext glibcc语言实现 2. resume coroutine: p...

dbd379717的博客 307
上一篇: C语言变长参数函数原理
下一篇: python安装requests模块失败的问题解决
code_peak
博客等级 码龄5年 403粉丝 67原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

code_peak

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值