我正在编写一种玩具编程语言,希望实现多任务协作.编译器和运行时是用C编写的.

我希望能够切换C调用堆栈,而不是使用线程.一、 我希望有多个调用堆栈"活动",并通过调用函数在它们之间切换.这在概念上应该类似于longjmp,但保留旧/新的调用堆栈.目标本质上是在其他语言中实现类似"异步"的运行时.

我想以"最简单"的方式在没有库的情况下做到这一点.我的 idea 基本上就是分配一个新的C调用堆栈并切换到它.在C中是否有任何方法可以以平台无关的方式(即不使用汇编)实现这一点?

我找到的最接近的东西是this answer中的一小块组件(链接自this question)

编辑:我所说的"平台无关"是指使用linux的硬件无关,尽管一般的Unix/BSD支持会更好.

推荐答案

不清楚什么样的答案会让你满意.

C语言本身并没有提供任何这样的功能,因此任何解决方案都将依赖于提供该功能的其他东西.

请注意,自C.2011以来,C在<threads.h>中引入了可选的线程支持,但没有线程属性操作可以让人们区分普通线程和轻量级线程执行.

可能是状态机,但需要操作系统的帮助.

如果您想知道如何仅使用C语言提供的原语来实现这一点,那么答案的 Select 几乎局限于实现您自己的状态机机制.但这需要您使用操作系统提供的机制,以便在访问通常会阻塞到完成的操作时启用非阻塞交互.这是为了让您有机会停驻当前执行上下文,并try 另一个执行上下文.

例如,在UNIX中,可以创建一个状态机数据 struct 来指示通过套接字发送文件的进度.

struct send_a_file_ctx {
    int socket;
    int file;
    size_t file_size;     /* total number of bytes to send */
    size_t bytes_sent;    /* bytes sent so far over the socket */
    size_t buffer_size;   /* number of file bytes in the buffer */
    size_t buffer_offset; /* number of file bytes from buffer sent */
    char buffer[];        /* buffered chunk of the file */
};

没有执行堆栈per se,但有一个 struct 跟踪缓冲区中字节传递到套接字的过程.

在UNIX中,send()调用可以传入MSG_DONTWAIT标志,以指示操作不应阻止完成,而应返回一个值,指示操作将被阻止.这将显示错误结果,errno设置为EAGAINEWOULDBLOCK.然后,应将此上下文 struct 保存在某个队列中,以便将来再次提取以重试该操作.同一队列中可能有其他上下文 struct ,在此基础上可以再次try send(),以查看是否可以取得进展.

UNIX提供了一些机制,允许您等待一个指示,表明您试图发送文件的一个或多个套接字将不再阻塞(请参阅pollselectepollkevent,以及可能的其他变体,具体取决于您的操作系统),而不是在重试时不断循环.

What about ucontext.h?

makecontext()swapcontext()函数是classic UNIX系统为协作并发提供的机制.它允许进程使用备用执行堆栈切换其当前执行堆栈.因为它允许您切换执行堆栈,所以可以避免定义状态机 struct 的需要.状态是执行堆栈的固有状态.

然而,它并不能避免需要启用与通常会阻塞到完成的操作的非阻塞交互.在收到操作将被阻止的指示后,您将切换到备用堆栈,以重试之前被阻止的操作.

同样,为了避免连续的重试,您仍然需要实现某种堆栈的调度队列来恢复,并使用解复用等待原语(如poll)来知道何时可以恢复哪个堆栈而不被阻塞.

虽然Linux和BSD系统仍然支持ucontext.h,但它不再是POSIX标准的一部分.它是原始POSIX的一部分.1-2001,但在POSIX中标记为过时.1-2004. 在POSIX中.2008年1月,已删除.

有什么好的 Select 吗?

这取决于你认为什么是"好的".

State machines?

如果您想要一些处理解复用的东西,并且只想编写代码来在恢复时运行状态机,那么可以考虑使用类似于libevent或其分支/克隆之一的东西.它提供了一个通用接口,用于向解复用器注册上下文,并可在各种操作系统上运行.

然而,您仍然需要自己管理非阻塞交互.

Like threads?

如果您想要一个像使用线程一样易于编程的解决方案,并且避免管理自己的非阻塞上下文调度,那么您可以try GNU的pthLibwire、Windows Fibers甚至pthreads.

  • GNU pth可以提供类似线程的编程范例.如果您遵循提供的API,那么看起来您的所有线程都在执行块到完成的操作,而pth实际上是在拦截调用,并在封面下执行非阻塞版本,并代表您切换上下文.

  • 如果LGPL限制太多,您可以考虑使用Libwire,它是根据MIT许可证发布的.这是一个用户空间线程库,旨在为C带来一个类似GoLang的协同编程接口.

  • Windows Fibers是一种提供轻量级线程的工具,同时保持从块到完成的编程风格.Fibers有一个值得注意的特性,即fiber可以转换为thread,然后再转换回fiber.

  • POSIX pthreadscontentionscope参数定义为pthread_attr_setscope.此参数的一个值为PTHREAD_SCOPE_PROCESS.这似乎是使用pthreads实现轻量级调度的方法.但是,此范围的行为是由实现定义的.

工具书类

C++相关问答推荐

va_arg -Linux和Windows上的不同行为

C中的整字母后缀i是什么

如何在C中通过转换为char * 来访问float的字节表示?

librsvg rsvg_handle_get_dimensions获取像素大小与浏览器中的渲染大小没有不同

来自stdarg.h的c中的va_args无法正常工作<>

当多个线程在C中写入相同的文件描述符时,如何防止争用情况?

为什么删除CAP_DAC_OVERRIDE后创建文件失败?

如何将长字符串转换为较小的缩写,该缩写由第一个字符、最后一个字符和中间的字符数组成?

如何在c++中包装返回空*的函数

在为hashmap创建加载器时,我的存储桶指向它自己

对于C中给定数组中的每个查询,如何正确编码以输出给定索引范围(1到N)中所有数字的总和?

C:在编译时构建和使用字符串文字的预处理器宏?

在vfork()之后,链接器如何在不 destruct 父内存的情况下解析execve()?

类型定义 struct 与简单的类型定义 struct

当我用scanf(&Q;%S%S%S&Q;,单词0,单词1,单词2)输入多个单词时,除了最后一个单词外,每个单词的第一个字符都丢失了

为什么argc和argv即使在主函数之外也能工作?

将不同类型的指针传递给函数(C)

GDB 跳过动态加载器代码

添加/删除链表中的第一个元素

如何修复数组数据与列标题未对齐的问题?