Go无法在Go 1.8之前动态加载代码。 我是基于插件的系统的大力支持者,在许多情况下,它们需要动态加载插件。 我什至考虑过编写基于C集成的插件包。
Go设计师将这种功能添加到了语言中,我感到非常兴奋。 在本教程中,您将学习为什么插件如此重要,当前支持哪些平台以及如何在程序中创建,构建,加载和使用插件。
Go插件的原理
Go插件可用于多种用途。 它们使您可以将系统分解为易于推理和测试的通用引擎,并且许多插件都遵循严格的接口并具有明确定义的职责。 可以独立于使用它们的主程序来开发插件。
该程序可以同时使用插件的不同组合,甚至可以使用同一插件的多个版本。 主程序和插件之间的清晰界限促进了松散耦合和关注点分离的最佳实践。
“插件”包
Go 1.8中引入的新“插件”程序包的范围和接口非常狭窄。 它提供Open()
函数来加载共享库,该共享库返回一个Plugin对象。 Plugin对象具有一个Lookup()
函数,该函数返回Symbol(空接口{})的帽子,可以对该插件公开的函数或变量进行类型声明。 而已。
平台支援
目前,该插件包仅在Linux上受支持。 但是,您将看到,有一些方法可以在任何操作系统上使用插件。
准备基于Docker的环境
如果您在Linux机器上进行开发,则只需要安装Go 1.8,就可以了。 但是,如果您使用的是Windows或macOS,则需要一个Linux VM或Docker容器。 要使用它,您必须首先安装Docker 。
安装Docker之后,打开控制台窗口并键入: docker run -it -v ~/go:/go golang:1.8-wheezy bash
此命令将~/go
处的本地$GOPATH
映射到容器内的/go
。 这样一来,我就可以使用主机上最喜欢的工具来编辑代码,并使其在容器内可用,以便在Linux环境中进行构建和运行。
有关Docker的更多信息,请在Envato Tuts +上查看我的“ Docker从零开始”系列:
创建一个Go插件
Go插件看起来像常规软件包,您也可以将其用作常规软件包。 仅当将其构建为插件时,它才成为插件。 这是几个实现Sort()
的插件 对整数切片进行排序的函数。
QuickSort插件
第一个插件实现了朴素的QuickSort算法。 该实现适用于具有唯一元素或重复项的切片。 返回值是指向整数切片的指针。 这对于对元素进行适当排序的排序函数很有用,因为它允许返回而无需复制。
在这种情况下,我实际上创建了多个临时切片,因此大部分效果都被浪费了。 我在这里牺牲性能以提高可读性,因为目标是演示插件而不是实现超高效算法。 逻辑如下:
- 如果项目为零或一项,则返回原始切片(已排序)。
- 选择一个随机元素作为钉子。
- 将小于桩的所有元素添加到以下切片中。
- 将大于桩的所有元素添加到上述切片中。
- 将等于桩的所有元素添加到中间切片。
此时,对中间片进行排序是因为其所有元素都是相同的(如果有重复的桩钉,此处将有多个元素)。 现在是递归部分。 它通过再次调用Sort()
对下面和上面的切片进行Sort()
。 当这些调用返回时,将对所有切片进行排序。 然后,只需附加它们即可得到完整的原始项目切片。
package main
import "math/rand"
func Sort(items []int) *[]int {
if len(items) < 2 {
return &items
}
peg := items[rand.Intn(len(items))]
below := make([]int, 0, len(items))
above := make([]int, 0, len(items))
middle := make([]int, 0, len(items))
for _, item := range items {
switch {
case item < peg:
below = append(below, item)
case item == peg:
middle = append(middle, item)
case item > peg:
above = append(above, item)
}
}
below = *Sort(below)
above = *Sort(above)
sorted := append(append(below, middle...), above...)
return &sorted
}
BubbleSort插件
第二个插件以幼稚的方式实现了BubbleSort算法。 BubbleSort通常被认为很慢,但是对于少量元素和一些小的优化,它通常击败了诸如QuickSort之类的更复杂的算法。
实际上,通常使用以QuickSort开头的混合排序算法,并且当递归达到足够小的数组时,该算法将切换为BubbleSort。 冒泡排序插件实现了具有与快速排序算法相同签名的Sort()
函数。 逻辑如下:
- 如果项目为零或一项,则返回原始切片(已排序)。
- 遍历所有元素。
- 在每次迭代中,迭代其余项目。
- 将当前项目与任何更大的项目交换。
- 在每次迭代结束时,当前项目将放置在适当的位置。
package main
func Sort(items []int) *[]int {
if len(items) < 2 {
return &items
}
tmp := 0
for i := 0; i < len(items); i++ {
for j := 0; j < len(items)-1; j++ {
if items[j] > items[j+1] {
tmp = items[j]
items[j] = items[j+1]
items[j+1] = tmp
}
}
}
return &items
}
构建插件
现在,我们需要构建两个插件来创建一个可共享的库,该库可以由我们的主程序动态加载。 要构建的命令是: go build -buildmode=plugin
由于我们有多个插件,因此我将每个插件放置在共享“插件”目录下的单独目录中。 这是plugins目录的目录布局。 在每个插件子目录中,都有源文件“ <algorithm> _plugin.go”和一个小的Shell脚本“ build.sh”来构建插件。 最终的.so文件进入父“ plugins”目录:
$ tree plugins
plugins
├── bubble_sort
│ ├── bubble_sort_plugin.go
│ └── build.sh
├── bubble_sort_plugin.so
├── quick_sort
│ ├── build.sh
│ └── quick_sort_plugin.go
└── quick_sort_plugin.so
* .so文件进入plugins目录的原因是主程序可以很容易地发现它们,这将在以后看到。 每个“ build.sh”脚本中的实际构建命令均指定输出文件应进入父目录。 例如,对于冒泡排序插件,它是:
go build -buildmode=plugin -o ../bubble_sort_plugin.so
加载插件
加载插件需要了解目标插件的位置(* .so共享库)。 这可以通过多种方式完成:
- 传递命令行参数
- 设置环境变量
- 使用知名目录
- 使用配置文件
另一个问题是主程序是否知道插件名称,或者是否动态发现某个目录中的所有插件。 在下面的示例中,程序希望在当前工作目录下有一个名为“ plugins”的子目录,并且它将加载找到的所有插件。
调用filepath.Glob("plugins/*.so")
。so filepath.Glob("plugins/*.so")
函数会返回plugins子目录中所有扩展名为“ .so”的文件, plugin.Open(filename)
会加载该插件。 如果出了什么问题,程序就会出现紧急情况。
package main
import (
"fmt"
"plugin"
"path/filepath"
)
func main() {
all_plugins, err := filepath.Glob("plugins/*.so")
if err != nil {
panic(err)
}
for _, filename := range (all_plugins) {
fmt.Println(filename)
p, err := plugin.Open(filename)
if err != nil {
panic(err)
}
}
}
在程序中使用插件
找到并加载插件仅是成功的一半。 插件对象提供给定符号名称的Lookup()
方法,该方法返回一个接口。 您需要在具体对象中声明该接口(例如,类似于Sort()
的函数)。 无法发现可用的符号。 您只需要知道它们的名称和类型,即可正确键入assert。
当符号是一个函数时,您可以在成功声明类型后像调用其他任何函数一样调用它。 下面的示例程序演示了所有这些概念。 它动态加载所有可用插件,而不知道那里有哪些插件,除了它们在“ plugins”子目录中。 接下来,在每个插件中查找“ Sort”符号,并将其类型声明为带有func([]int) *[]int
签名的func([]int) *[]int
。 然后,对于每个插件,它使用整数切片调用sort函数并打印结果。
package main
import (
"fmt"
"plugin"
"path/filepath"
)
func main() {
numbers := []int{5, 2, 7, 6, 1, 3, 4, 8}
// The plugins (the *.so files) must be in a 'plugins' sub-directory
all_plugins, err := filepath.Glob("plugins/*.so")
if err != nil {
panic(err)
}
for _, filename := range (all_plugins) {
p, err := plugin.Open(filename)
if err != nil {
panic(err)
}
symbol, err := p.Lookup("Sort")
if err != nil {
panic(err)
}
sortFunc, ok = symbol.(func([]int) *[]int)
if !ok {
panic("Plugin has no 'Sort([]int) []int' function")
}
sorted := sortFunc(numbers)
fmt.Println(filename, sorted)
}
}
Output:
plugins/bubble_sort_plugin.so &[1 2 3 4 5 6 7 8]
plugins/quick_sort_plugin.so &[1 2 3 4 5 6 7 8]
结论
“插件”包为编写复杂的Go程序奠定了良好的基础,该程序可以根据需要动态加载插件。 编程界面非常简单,并且需要插件界面上使用程序的详细知识。
可以在“插件”包的顶部构建更高级且用户友好的插件框架。 希望它将很快移植到所有平台。 如果您在Linux上部署系统,请考虑使用插件来使您的程序更加灵活和可扩展。
翻译自: https://code.tutsplus.com/tutorials/writing-plugins-in-go--cms-29101