协程

更新时间:
2024-03-14
下载文档

协程

协程,又称作协同程序,是比线程还小的可执行代码序。

一个线程内可以拥有多个协程,这些协程共享线程除了栈之外的所有资源,例如优先级、内核对象等。由于线程内的所有协程共享线程本身的内核对象,所以调度器本身并不知道协程的存在,协程是靠所属线程被调度时执行的。一个线程内部的协程不可被抢占,只能轮转运行,只有当前正在运行的协程主动放弃处理器时,同线程内的其他协程才能获得处理器。当线程被删除时,线程内的所有协程也同时被删除。

SylixOS 内核支持协程,而不是使用第三方库模拟,这样使得 SylixOS 内部的协程管理更加便捷高效。

调用 Lw_Coroutine_Create 函数将在当前线程创建一个协程。需要注意的是,每一个线程的创建都会默认创建一个起始协程,因此线程总是从这个起始协程开始运行。

#include <SylixOS.h>
PVOID  Lw_Coroutine_Create(PCOROUTINE_START_ROUTINE    pCoroutineStartAddr,
                           size_t                      stStackByteSize,
                           PVOID                       pvArg);

函数 Lw_Coroutine_Create 原型分析:

  • 此函数成功返回协程控制指针,失败返回 LW_NULL 并设置错误号。
  • 参数 pCoroutineStartAddr 是协程启动地址。
  • 参数 stStackByteSize 是协程栈大小。
  • 参数 pvArg 是入口参数。

调用 Lw_Coroutine_Delete 函数将删除一个指定的协程,如果删除的是当前协程系统将直接调用 Lw_Coroutine_Exit 函数退出。当线程中的最后一个协程退出时该线程将退出。

#include <SylixOS.h>
ULONG  Lw_Coroutine_Delete(PVOID  pvCrcb);
ULONG  Lw_Coroutine_Exit(VOID); 

函数 Lw_Coroutine_Delete 原型分析:

  • 此函数成功返回 0,失败返回错误号。
  • 参数 pvCrcb 是协程控制指针。

函数 Lw_Coroutine_Exit 原型分析:

  • 此函数成功返回 0,失败返回错误号。

前面我们曾说过,调度器并不知道协程的存在,而协程之间则以轮转的方式运行,这决定了协程的调度必须由用户程序去管理。SylixOS 提供了以下函数来改变协程的调度顺序。

#include <SylixOS.h>
VOID   Lw_Coroutine_Yield(VOID);
ULONG  Lw_Coroutine_Resume(PVOID  pvCrcb);

函数 Lw_Coroutine_Resume 原型分析:

  • 此函数成功返回 0,失败返回非 0 值。
  • 参数 pvCrcb 是协程控制指针。

调用 Lw_Coroutine_Yield 函数可以主动放弃处理器,调用 Lw_Coroutine_Resume 函数使指定的协程恢复。

调用 Lw_Coroutine_StackCheck 函数可以对协程栈进行检查。

#include <SylixOS.h>
ULONG  Lw_Coroutine_StackCheck(PVOID         pvCrcb,
                               size_t       *pstFreeByteSize,
                               size_t       *pstUsedByteSize,
                               size_t       *pstCrcbByteSize);

函数 Lw_Coroutine_StackCheck 原型分析:

  • 此函数成功返回 0,失败返回错误号。
  • 参数 pvCrcb 是协程控制指针。
  • 输出参数 pstFreeByteSize 返回空闲栈大小。
  • 输出参数 pstUsedByteSize 返回使用栈大小。
  • 输出参数 pstCrcbByteSize 返回协程控制块大小。

参数 pstFreeByteSize、pstUsedByteSize、pstCrcbByteSize 可以为 NULL,如果相应的参数为 NULL,则不会关心指定类型的栈情况。

需要注意的是,如果检查协程栈的使用情况,则协程的父系线程必须使用栈检查选项如下表所示。

宏名解释
LW_OPTION_THREAD_STK_CHK运行时对线程栈进行检查
LW_OPTION_THREAD_STK_CLR在线程建立时栈数据清零
LW_OPTION_THREAD_USED_FP保存浮点运算器
LW_OPTION_THREAD_SUSPEND建立线程后阻塞
LW_OPTION_THREAD_INIT初始化线程
LW_OPTION_THREAD_SAFE建立的线程为安全模式
LW_OPTION_THREAD_DETACHED线程不允许被合并
LW_OPTION_THREAD_UNSELECT此线程不使用 select 功能
LW_OPTION_THREAD_NO_MONITOR内核跟踪器对此线程不起效
LW_OPTION_THREAD_ UNPREEMPTIVE任务不可抢占(当前不支持)
LW_OPTION_THREAD_SCOPE_PROCESS进程内竞争(当前不支持)

下面程序展示了 SylixOS 协程的使用方法。

#include <SylixOS.h>
#include <stdio.h>

VOID  coroutine0 (PVOID  pvArg)
{
    INT  i;
    for (i = 0; i < 5; i++) {
        fprintf(stdout, "coroutine0 running...\n");
        Lw_Time_SSleep(1);
    }
}

VOID  coroutine1 (PVOID  pvArg)
{
    INT  i;
    for (i = 0; i < 5; i++) {
        fprintf(stdout, "coroutine1 running...\n");
        Lw_Time_SSleep(1);
    }
}

PVOID  tTest (PVOID  pvArg)
{
    PVOID  pcCrcb0, pcCrcb1;

    pcCrcb0 = Lw_Coroutine_Create(coroutine0, 4 * 1024, LW_NULL);
    if (pcCrcb0 == LW_NULL) {
        return  (LW_NULL);
    }
    pcCrcb1 = Lw_Coroutine_Create(coroutine1, 4 * 1024, LW_NULL);
    if (pcCrcb1 == LW_NULL) {
        return  (LW_NULL);
    }
    Lw_Coroutine_Yield();                                  /* 使其他协程运行         */
    while (1) {
        Lw_Time_SSleep(10);
    }
    return  ((PVOID)1);
}

int main (int argc, char *argv[])
{
    LW_HANDLE  hId;

    hId = Lw_Thread_Create("t_test", tTest, LW_NULL, LW_NULL);
    if (hId == LW_HANDLE_INVALID) {
        return  (PX_ERROR);
    }
    Lw_Thread_Join(hId, LW_NULL);

    return  (ERROR_NONE);
}

在 SylixOS Shell 下运行程序。

# ./Synergetics
coroutine0 running...
coroutine0 running...
coroutine0 running...
coroutine0 running...
coroutine0 running...
coroutine1 running...
coroutine1 running...
coroutine1 running...
coroutine1 running...
coroutine1 running...

注意:
使用 Lw_Coroutine_Create 函数创建协程时,需要根据不同平台的堆栈大小设置 size_t。

从运行结果可以看出 coroutine0 先获得了执行,并且在 coroutine0 运行期间并没有因为延迟阻塞而被打断,下面我们来分析一下这个过程。

在 tTest 线程中,程序首先创建 coroutine0 协程,然后创建 coroutine1 协程,之前介绍了线程创建的时候都会默认创建一个起始协程,因此线程首先从起始协程开始运行,这里程序调用 Lw_Coroutine_Yield 函数来主动放弃处理器,此时 coroutine0 获得处理器开始运行,由于协程之间是轮转运行,所以在 coroutine0 完成运行之前并不会主动放弃处理器(程序并没有主动调用 Lw_Coroutine_Yield 函数,同时也证明一点调度器感知不到协程的存在),当 coroutine0 运行完成后,自动切换到了 coroutine1 协程运行。这也说明先创建的协程会优先得到处理器,符合先进先出的原则。

文档内容是否对您有所帮助?
有帮助
没帮助