如何在 Go 中使用 Wasm:浅聊 WebAssembly

8,314 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情

最近学习了Wasm,吸收了很多知识,因此做一个记录和大家分享

文章大纲:

Go Wasm 实战.png

一、前言

随着 Web 应用的不断发展,越来越多的开发者开始使用 Go 和 Wasm 来构建高效、安全的 Web 应用。本文的目的是帮助读者快速了解 Go 和 Wasm 的基本概念和实战技巧,使其能够快速上手 Go 和 Wasm 开发。

1.1 本文代码开发环境

系统:Windows 11 22H2
IDE:VSCode、GoLand

1.2 基本概念

为了更好的帮助读者理解Wasm,这里给出一些相关的定义

Wasm (WebAssembly) 是一种运行在 Web 浏览器上的高效、安全的编程语言,旨在为 Web 应用提供一个更快、更安全、更灵活的运行环境。Wasm 通过提供二进制的格式和相关的 API,使得开发者能够在 Web 浏览器上运行 C/C++ 等语言编写的代码,从而实现更快、更高效的 Web 应用。

Wasm 的优势在于:

  1. 性能:Wasm 代码在 Web 浏览器上运行速度比 JavaScript 快得多,这使得开发者能够在 Web 浏览器上实现更复杂、更多功能的应用。
  2. 安全:Wasm 代码运行在沙箱环境中,从而保证了应用的安全性。
  3. 可移植性:Wasm 代码是二进制的,可以在任何支持 Wasm 的平台上运行,从而实现代码的可移植性。
  4. 灵活性:Wasm 支持多种语言的编写,包括 C/C++、Rust、Go 等,这使得开发者能够使用自己熟悉的语言编写 Web 应用。

在现实中,Wasm 已经被广泛应用在游戏开发、虚拟和增强现实等领域,并且有越来越多的 Web 开发者开始使用 Wasm 进行实际项目的开发。

于是,简单来说,Wasm是一种高效的Web编程语言。

二、开发环境搭建

2.1 安装 Go 和 Wasm 工具链

  1. 安装 Go:Go 官方网站提供了详细的安装说明,包括 Windows、macOS 和 Linux 系统的安装方法。请访问 golang.org/dl 获取安装说明。
image.png

输入以下命令,正确输出版本号则安装成功

go version
image.png
  1. 安装并配置 TinyGo。TinyGo是一个编译 Go 代码为 WebAssembly 的工具,其官方网站提供了详细的安装说明,包括 Windows、macOS 和 Linux 系统的安装方法。请访问 Quick install guide | TinyGo 获取安装说明。
image.png

输入以下命令,正确输出版本号则安装成功

tinygo version

image.png

2.2 编译 Wasm模块

  1. 编写 Go 代码并使用 TinyGo 编译为 Wasm 二进制文件。

main.go:

package main

func main() {
   println("Hello world!")
}

使用 TinyGo 编译:

tinygo build -o ./wasm.wasm -target wasm .\main.go

没有意外的话这里会报错:

error: could not find wasm-opt, set the WASMOPT environment variable to override

wasm-opt 是 WebAssembly 的优化器,TinyGo 可以使用它来优化生成的 Wasm 代码。报错提示没有wasm-opt,因为最新版本已经更换,原因如下:Windows release missing wasm-opt.exe · Issue #2601 · tinygo-org/tinygo (github.com)

解决方法直接下载

WebAssembly/binaryen: Optimizer and compiler/toolchain library for WebAssembly (github.com)

设置环境变量重启你的终端和IDE,重新编译即可

image.png

三、介绍 Wasmer 库

3.1 Wasmer简介

Wasmer 是一个高性能的 WebAssembly(Wasm)运行时。它支持多种语言(如 Rust、C、C++、Go、JavaScript 等)编写的 WebAssembly 模块运行在各种环境(如浏览器、服务器、命令行)中。

Wasmer 的优点在于:

  1. 它提供了高效的加载和执行速度,比传统的 JavaScript 快数十倍以上。
  2. 它可以在不同环境中(例如,浏览器、服务器、命令行)中使用,并且它的 API 是统一的,无需考虑不同环境的差异。
  3. 它支持多种语言(如 Rust、C、C++、Go、JavaScript 等)编写的 WebAssembly 模块,并且可以在不同语言间实现更高效的交互。

使用 Wasmer 库需要先安装相应的工具(例如,Rust、JavaScript),然后通过相应工具的包管理工具(例如,npm)安装 Wasmer 库。具体示例代码请参考 Wasmer 库的官方文档。

3.2Wasmer 使用

Wasmer用法有很多,我用的最多的一个参数是run,它允许我们在使用wasmer运行时,去执行已经编译好的产物。

以前文编译好的main.wasm为例,在main.wasm目录下打开终端,执行以下命令:

wasmer run main.wasm

执行结果如下:

image.png

至于wasmer其他的用法,我暂时还没有用到,等以后用到了我会回来更新这部分内容。

四、一个简单的🌰

4.1 Hello World

首先创建main.go:

//+build wasm,js

package main

import (
   "fmt"
   "time"
)

func exampleFunc() {
   num := 0
   for {
      fmt.Println("Hello from Web Assembly. Number of times this goroutine has ran: ", num)
      time.Sleep(1 * time.Second)
      num++
   }
}

func main() {
   // This exitChan blocks the program from returning the main function.
   // Without it, your program will exit before the goroutine can complete its execution.
   // This is useful if you want your goroutines to continually run.
   exitChan := make(chan string)
   go exampleFunc()
   <-exitChan
}

然后创建index.html:

<html>
<title>Web Assembly Work</title>
<head>
    <meta charset="utf-8">
    <script src="wasm_exec.js"></script>
    <script>
        const go = new Go();
        WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
            go.run(result.instance);
        });
    </script>
</head>
<body>
<div id="main">
    <center>
        Check the javascript console output of your browser to ensure execution.
    </center>
</div>
</body>
</html>

同时将wasm_exec.js从官方包复制到项目中来

cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .

创建http-server,编写一个简单的服务器:

// A basic HTTP server.
// By default, it serves the current working directory on port 12000.
package main

import (
	"flag"
	"log"
	"net/http"
)

var (
	listen = flag.String("listen", ":12000", "listen address")
	dir    = flag.String("dir", "./public", "directory to serve")
)

func main() {
	flag.Parse()
	log.Printf("listening on %q...", *listen)
	err := http.ListenAndServe(*listen, http.FileServer(http.Dir(*dir)))
	log.Fatalln(err)
}

整理代码:

image.png

使用 TinyGo 编译:

tinygo build -o public/main.wasm -target wasm .\main.go

启动内置服务器:

go run utilities/http-server.go
image.png

打开浏览器,访问localhost:12000/index.html,检查F12控制台:

image.png

4.2 技巧和注意事项:

  1. 注意性能:Wasm 可能不如本地代码快,因此需要仔细监控代码的性能,并对其进行优化。
  2. 注意浏览器兼容性:Wasm 目前在所有主流浏览器中都受到支持,但是在一些旧版本的浏览器中可能不受支持。因此,在使用 Wasm 时需要注意浏览器兼容性。(特别吐槽一句:我在写这篇文章的时候用Edge做的测试,代码运行一直有错,调试半天没看出啥问题,后来换了火狐运行正常了……,总之避坑Edge
  3. 选择合适的库:Go 和 Wasm 中有很多可用的库,但由于开发环境不同,有的不支持Windows或者支持度不高,因此需要根据项目的需求选择合适的库。(嘛,虽然WSL2也挺方便的)
  4. 调试:在使用 Wasm 时,调试可能会变得更加困难,因此需要使用合适的调试工具来帮助解决问题。(我个人推荐GoLand,真的超好用!!!)

五、最后

5.1 总结

本文介绍了WebAssembly的一些基本概念,和WebAssembly开发环境的搭建,使用Go和Wasm在控制台打印属于WebAssembly的Hello World,读者可以根据本文的步骤,自行实现这个小例子。

5.2 参考资料