1. 什么是循环依赖
Go 中的循环依赖是指两个或多个包之间相互引用,形成了一个循环依赖关系。这种情况下,包 A 依赖包 B,同时包 B 也依赖包 A,导致两个包之间无法明确地确定编译顺序,从而可能引发编译错误或其他问题。循环依赖是 Go 中需要小心处理的问题,因为它可能导致程序不可编译或产生未定义行为。
2. Go中为什么不容易出现循环依赖
在 Go 中出现循环依赖的情况相对较少,但也可能会发生。这主要是因为 Go 编译器的工作方式以及包管理机制导致的。
- 单一源文件编译:Go 编译器在编译源文件时,会先将源文件分解为独立的包单元,然后对每个包单元进行编译。这意味着每个源文件都必须能够独立编译,而不能依赖于其他源文件。因此,如果出现循环依赖,编译器无法解决这种依赖关系,因为它需要单独编译每个包。
- 包管理机制:Go 的包管理机制要求每个包都必须明确导入它所依赖的其他包。如果两个或多个包相互依赖,且它们在导入彼此时形成了循环依赖,编译器将无法确定包的加载顺序,因为每个包都依赖于另一个尚未加载的包。
尽管 Go 编译器和包管理机制都能够帮助开发人员避免循环依赖,但仍然有一些情况可能导致循环依赖的出现:
3. 全局存储需要相互依赖的函数
通过map关键字调用
go
package callback_mgr
import (
"fmt"
"reflect"
)
var callBackMap map[string]interface{}
func init() {
callBackMap = make(map[string]interface{})
}
func RegisterCallBack(key string, callBack interface{}) {
callBackMap[key] = callBack
}
func CallBackFunc(key string, args ...interface{}) []interface{} {
if callBack, ok := callBackMap[key]; ok {
in := make([]reflect.Value, len(args))
for i, arg := range args {
in[i] = reflect.ValueOf(arg)
}
outList := reflect.ValueOf(callBack).Call(in)
result := make([]interface{}, len(outList))
for i, out := range outList {
result[i] = out.Interface()
}
return result
} else {
panic(fmt.Errorf("callBack(%s) not found", key))
}
}
- callback_mgr 包声明了一个包名 callback_mgr。
- 导入了两个标准库包:fmt 和 reflect。fmt 用于格式化输出,reflect 用于在运行时进行类型信息的查看和操作。
- 声明了一个全局变量 callBackMap,它是一个 map,用于存储回调函数,键是字符串类型的标识符,值是接口类型,可以存储任意类型的回调函数。
- 在 init() 函数中对 callBackMap 进行了初始化,将其设为一个空 map。
- RegisterCallBack 函数用于注册回调函数。它接受两个参数:key,用于标识回调函数;callBack,实际的回调函数。这个函数将回调函数存储在 callBackMap 中,使用 key 作为键。
- CallBackFunc 函数用于调用注册的回调函数。它接受一个 key 参数来标识要调用的回调函数,以及一个可变数量的 args 参数,用于传递给回调函数的参数。如果找到了注册的回调函数,则使用 reflect 包动态调用该函数,并将参数传递给它。然后将返回的结果转换为 interface{} 类型并返回。如果未找到注册的回调函数,则会抛出一个带有错误消息的 panic。
3.如何调用
go
result := callback_mgr.CallBackFunc("methodName", "args....")
//返回值result 将会是一个 []interface{} 类型的切片,需要将其转换成对应的类型
result[0].(structType)