
WAMR 模块实例上下文 API 与 wasi-threads 交互实战深入解析 inst-context-threads 示例【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit导读本指南以 WAMRWebAssembly Micro Runtime版本 2.4.1随本仓库以lib/wasm-micro-runtime-WAMR-2.4.1方式内嵌中samples/inst-context-threads示例为主线完整讲解「模块实例上下文Module Instance ContextAPI」与「wasi-threads 多线程」如何协同工作当 Wasm 应用内部创建多个线程时宿主原生函数如何把一份上下文数据写入并扩散到该模块实例派生出的所有线程实现跨线程共享。读完本文你将掌握wasm_runtime_create_context_key、wasm_runtime_set_context_spread、wasm_runtime_get_context等 API 的完整使用流程、WAMR 内部的扩散实现原理以及如何构建和运行该示例。示例概览它到底演示了什么官方 README见 lib/wasm-micro-runtime-WAMR-2.4.1/samples/inst-context-threads/README.md对示例的定位非常凝练This sample demonstrates some interactions between module instance context API and wasi-threads.即模块实例上下文 API 与 wasi-threads 之间的交互。具体来说示例回答了一个在多线程 Wasm 场景下非常现实的问题当一个 Wasm 模块实例内部用pthread_create派生出多个线程后宿主侧通过上下文 API 写入的数据能否被该实例的所有线程看到示例给出的答案和实现要点是Wasm 应用testapp.c内部创建pthread线程线程从宿主导入的get_context读到的初始值应为「未设置」返回 -1线程内调用宿主导入的set_context(1234)把值写入上下文线程回读确认值为 1234主线程pthread_join之后回读同样得到 1234——证明通过set_context_spread写入的上下文会扩散到模块实例对应的线程簇cluster中的所有执行环境。整个示例目录结构如下lib/wasm-micro-runtime-WAMR-2.4.1/samples/inst-context-threads/ ├── CMakeLists.txt # 宿主运行时的构建配置开启 WASI threads、AOT/解释器 ├── README.md # 官方示例说明本文的主体 ├── run.sh # 运行脚本执行 out/inst-context -f out/wasm-apps/testapp.wasm ├── build.sh # 一键构建脚本编译宿主程序与 wasm 应用 ├── src/ │ ├── main.c # 宿主侧主程序运行时初始化、注册原生符号、加载/实例化模块 │ ├── native_impl.c # 原生函数实现set_context / get_context │ └── my_context.h # 自定义上下文结构体与全局声明 └── wasm-apps/ └── testapp.c # Wasm 侧应用使用 pthread 导入的上下文 API宿主侧实现上下文 API 的完整调用链宿主程序由src/main.c见 main.c与src/native_impl.c见 native_impl.c组成。第一步注册自定义上下文键main.c在wasm_runtime_full_init成功后立即创建上下文键my_context_key wasm_runtime_create_context_key(my_context_dtor); if (!my_context_key) { printf(wasm_runtime_create_context_key failed.\n); return -1; }要点说明wasm_runtime_create_context_key的签名是void *wasm_runtime_create_context_key(void (*dtor)(WASMModuleInstanceCommon *inst, void *ctx))返回值是一个不透明的 key 句柄后续所有 set/get 操作都以它为索引可以同时注册一个析构回调dtor当模块实例被销毁deinstantiate时WAMR 会调用它清理该 key 关联的上下文数据。示例中的my_context_dtor做了两个断言void my_context_dtor(wasm_module_inst_t inst, void *ctx) { printf(%s called\n, __func__); my_dtor_called; bh_assert(ctx my_context); /* 传回的正是全局上下文对象 */ bh_assert(inst module_inst); /* 且关联的正是当前模块实例 */ }这验证了析构回调的触发时机与参数正确性主程序在wasm_runtime_deinstantiate前后分别断言my_dtor_called 0与my_dtor_called 1见 main.c从而严格证明析构回调只会在实例销毁时被调用一次。第二步注册原生函数并导出到 Wasm 侧示例通过NativeSymbol数组把两个 C 函数导出给 Wasm 应用模块名envstatic NativeSymbol native_symbols[] { { set_context, set_context, (i), NULL }, { get_context, get_context, ()i, NULL }, }; init_args.n_native_symbols sizeof(native_symbols) / sizeof(NativeSymbol); init_args.native_module_name env; init_args.native_symbols native_symbols;(i)表示set_context接收一个i32参数、无返回值()i表示get_context无参数、返回一个i32符号签名语法与doc/export_native_api.md描述的原生 API 导出规范一致数组必须声明为 static示例中注释明确说明「the array must be static defined since runtime will keep it after registration」因为运行时在注册后会长期持有该数组。第三步原生函数如何读写上下文native_impl.c中的两个函数是本示例的核心void set_context(wasm_exec_env_t exec_env, int32_t n) { wasm_module_inst_t inst wasm_runtime_get_module_inst(exec_env); printf(%s called on module inst %p\n, __func__, inst); struct my_context *ctx my_context; ctx-x n; wasm_runtime_set_context_spread(inst, my_context_key, ctx); } int32_t get_context(wasm_exec_env_t exec_env) { wasm_module_inst_t inst wasm_runtime_get_module_inst(exec_env); struct my_context *ctx wasm_runtime_get_context(inst, my_context_key); if (ctx NULL) { return -1; /* 尚未设置上下文时的约定返回值 */ } return ctx-x; }关键点通过wasm_runtime_get_module_inst(exec_env)拿到当前执行环境对应的模块实例这是上下文 API 的「归属对象」——上下文始终挂在模块实例上而不是全局变量wasm_runtime_set_context_spread是「扩散」版本它会把上下文写入该模块实例及其派生线程对应的所有执行环境见下文的内部实现分析wasm_runtime_get_context在 key 未被写入时返回NULLWasm 侧据此约定-1表示「未设置」上下文数据结构定义在 my_context.h 中非常精简struct my_context { int x; }; extern void *my_context_key; extern struct my_context my_context;这里my_context是宿主侧的一个全局对象其指针被写入到每个线程的实例上下文中。Wasm 侧实现多线程视角验证上下文传播Wasm 应用 testapp.c 使用pthread直接编写导入两个函数void set_context(int32_t n) __attribute__((import_module(env))) __attribute__((import_name(set_context))); int32_t get_context() __attribute__((import_module(env))) __attribute__((import_name(get_context)));线程函数start的执行逻辑带断言验证void * start(void *vp) { int32_t v; printf(thread started\n); /* 新线程初始状态上下文未设置应为 -1 */ v get_context(); assert(v -1); /* 在线程内写入上下文 */ set_context(1234); /* 线程内回读应为 1234 */ v get_context(); assert(v 1234); return NULL; }主函数则验证「线程写入后主线程也能读到」这一传播语义int main() { pthread_t t1; int32_t v; /* 初始状态主线程未设置上下文 */ v get_context(); assert(v -1); /* 创建并等待线程执行完毕 */ ret pthread_create(t1, NULL, start, NULL); assert(ret 0); ret pthread_join(t1, val); assert(ret 0); /* 关键断言上下文已从线程扩散回主线程 */ v get_context(); assert(v 1234); printf(success\n); return 0; }整个测试流程对应了三个递进式的验证点验证点断言说明初始未设置get_context() -1上下文键创建后默认无数据NULL映射为-1线程内写入与回读写入 1234 后get_context() 1234set_context_spread至少对本线程执行环境生效跨线程传播主线程 join 后get_context() 1234上下文扩散到整个线程簇主/子线程共享提示示例中的 Wasm 代码直接使用pthread_create/pthread_join这正是 wasi-threads 提案提供的 API 形态——WAMR 通过lib-wasi-threads库将其映射到宿主线程实现详见 thread_manager.c。内部实现原理上下文如何「扩散」到所有线程理解wasm_runtime_set_context_spread的底层实现才能真正明白本示例的价值。该函数的公开声明位于 wasm_runtime_common.h实现位于 wasm_runtime_common.cwasm_runtime_set_context_spread(WASMModuleInstanceCommon *inst, void *key, void *ctx) { wasm_native_set_context_spread(inst, key, ctx); }而wasm_native_set_context_spread见 wasm_native.c的关键分支是void wasm_native_set_context_spread(WASMModuleInstanceCommon *inst, void *key, void *ctx) { #if WASM_ENABLE_THREAD_MGR ! 0 wasm_cluster_set_context(inst, key, ctx); #else wasm_native_set_context(inst, key, ctx); #endif }开启线程管理器WASM_ENABLE_THREAD_MGR时走wasm_cluster_set_context做集群级扩散未开启线程支持时退化为只写当前实例等价于普通wasm_runtime_set_context。线程簇cluster遍历实现扩散的核心wasm_cluster_set_context位于 thread_manager.cvoid wasm_cluster_set_context(WASMModuleInstanceCommon *module_inst, void *key, void *ctx) { WASMExecEnv *exec_env wasm_clusters_search_exec_env(module_inst); if (exec_env NULL) { /* Maybe threads have not been started yet. */ wasm_runtime_set_context(module_inst, key, ctx); } else { WASMCluster *cluster; struct inst_set_context_data data; data.key key; data.ctx ctx; cluster wasm_exec_env_get_cluster(exec_env); bh_assert(cluster); os_mutex_lock(cluster-lock); traverse_list(cluster-exec_env_list, set_context_visitor, data); os_mutex_unlock(cluster-lock); } }从中可以提炼出以下实现事实运行时为每个 Wasm 模块实例以及它派生的线程执行环境WASMExecEnv维护一个线程簇WASMCluster簇内通过exec_env_list链表挂载所有执行环境wasm_cluster_set_context先在集群中查找该实例对应的执行环境若线程尚未启动找不到则退化为单实例写入wasm_runtime_set_context若线程已经启动则在持有cluster-lock的情况下遍历整个exec_env_list通过set_context_visitor对每一个执行环境调用wasm_runtime_set_context写入同一份上下文——这正是「spread扩散」语义的来源加锁遍历保证了在多线程并发调用set_context时对上下文列表访问的一致性。上下文键与析构回调的底层存储在 wasm_native.c 中上下文键的实现是一个「句柄即索引」的方案g_context_dtors[WASM_MAX_INSTANCE_CONTEXTS]是一个全局静态数组保存每个 key 的析构回调wasm_native_create_context_key线性扫描数组中第一个空位把 key 编码为idx 1避免 0 冲突并注册析构回调未提供时使用dtor_noop空实现wasm_native_destroy_context_key回收该槽位wasm_native_set_context/wasm_native_get_context通过context_key_to_idx把 key 还原为索引然后读写WASMModuleInstanceExtraCommon中的contexts[idx]数组。也就是说每个模块实例的扩展公共结构体里都有一个「上下文槽位数组」key 决定槽位set/get 决定读写而 spread 决定写入范围。构建与运行从源码到可执行程序前提条件构建宿主程序需要 CMakecmake_minimum_required (VERSION 3.14)见 CMakeLists.txt构建 Wasm 应用需要wasi-sdk20.0 或更高版本必须带 wasi-threads 支持因为示例使用了--targetwasm32-wasi-threads与-pthread编译选项见 build.sh宿主平台为 Linux/BSD/macOS 等支持 pthread 的系统Windows 平台下 CMake 工程会被调整为C ASM语言组合见 CMakeLists.txt。构建步骤执行仓库内的 build.shcd lib/wasm-micro-runtime-WAMR-2.4.1/samples/inst-context-threads ./build.sh脚本依次完成在cmake_build/下执行cmake ..与make构建宿主可执行文件inst-context并拷贝到out/目录进入wasm-apps/目录用 wasi-sdk 的 clang 编译所有.c文件为.wasm/opt/wasi-sdk/bin/clang \ --targetwasm32-wasi-threads \ -pthread \ -Wl,--import-memory \ -Wl,--export-memory \ -Wl,--max-memory655360 \ -o out/wasm-apps/testapp.wasm wasm-apps/testapp.c其中--import-memory/--export-memory让 Wasm 内存与宿主共享、可供线程间可见--max-memory655360预留给多线程栈/堆空间。CMake 侧的运行时特性开关宿主程序构建时通过 CMake 显式开启了以下特性见 CMakeLists.txtset (WAMR_BUILD_INTERP 1) # 解释器模式 set (WAMR_BUILD_AOT 1) # AOT 模式 set (WAMR_BUILD_JIT 0) # 关闭 JIT set (WAMR_BUILD_LIBC_BUILTIN 0) # 关闭内置 libc set (WAMR_BUILD_LIB_WASI_THREADS 1) # 开启 wasi-threads 支持本示例的关键 if (NOT MSVC) set (WAMR_BUILD_LIBC_WASI 1) # 开启 WASI libc endif ()值得注意WAMR_BUILD_LIB_WASI_THREADS是让pthread_create等调用真正可用的前提而运行时为 vmlib 链接了-lpthread宿主程序最终链接vmlib -lm -ldl -lpthreadLinux 上还会附加-lrt。运行与预期输出运行脚本 run.sh 内容即out/inst-context -f out/wasm-apps/testapp.wasm其中-f指定 Wasm 文件路径参见main.c中print_usage输出的Options: -f [path of wasm file]。按执行顺序预期输出大致为各printf由原生函数与 Wasm 侧代码共同产生thread started confirming the initial state on thread get_context called on module inst 0x... confirming the context on thread set_context called on module inst 0x... ... confirming the context propagated from the thread on main get_context called on module inst 0x... success程序以退出码 0 结束的前提是所有assert全部通过包括新线程初始状态下get_context()返回 -1线程内set_context(1234)后回读为 1234主线程pthread_join后回读仍为 1234上下文已从线程扩散到主线程实例销毁时my_context_dtor恰好被调用一次。与同系列示例的对照单实例版本的差异本仓库还提供了一个不含线程的姊妹示例 samples/inst-context同样包含src/main.c与src/native_impl.c并使用了相同的上下文 API。两者的核心差异在于inst-context仅演示单个模块实例上的上下文 set/get 基本语义不存在线程因此即使使用wasm_runtime_set_context_spread也只会落到单实例写入路径wasm_clusters_search_exec_env找不到已启动线程时会走退化分支inst-context-threads在 wasi-threads 场景下验证扩散语义证明set_context_spread会沿线程簇把上下文同步给所有执行环境。如果你需要对比「不扩散」与「扩散」两种 API 的行为差异可以同时阅读这两个示例的native_impl.c观察wasm_runtime_set_context仅写当前实例与wasm_runtime_set_context_spread写整个集群在源码层面的调用差异。小结与工程启示通过inst-context-threads示例可以总结出模块实例上下文 API 的完整工程用法创建上下文键wasm_runtime_create_context_key(dtor)在运行时初始化后调用一次注册自定义上下文槽位与析构回调注册原生符号通过NativeSymbol数组 init_args.n_native_symbols把宿主函数导出到env模块签名语法遵循原生 API 导出规范写入与读取原生函数内用wasm_runtime_get_module_inst(exec_env)定位实例再调用wasm_runtime_set_context_spread/wasm_runtime_get_context读写上下文析构清理wasm_runtime_destroy_context_key在运行时销毁前回收 key模块实例销毁时 dtor 精确触发一次可用于释放宿主侧资源多线程语义set_context_spread在多线程场景下通过线程簇遍历wasm_cluster_set_context加锁遍历exec_env_list实现跨线程上下文同步这是它区别于普通set_context的本质所在。对于在 Wasm 中承载多线程业务、同时又需要宿主侧按线程簇注入配置或状态的场景例如日志上下文、租户标识、请求级元数据的跨线程传播这一套 API 提供了规范且线程安全的宿主侧解决方案而本示例的完整代码正是最直接的可复现参考。【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考