Go包导入流程与重复初始化技术咨询
Go包导入与初始化疑问
我是Go语言新手,正在研究包及其导入机制,阅读了一篇Medium上的文章(搜索“golang packages”排名第一,获7k+点赞),但实际测试后遇到了疑问。
项目结构
pkg ├── mypkg | └── mypkg.go | ├── pkg1 | └── pkg1.go | └── pkg2 └── pkg2.go go.mod main.go
各文件内容
pkg1.go
package pkg1 import ( "fmt" "mymod/pkg/mypkg" ) func init() { fmt.Println("Hello everyone from pkg1 init") } func HelloFromPkg1() { fmt.Println("Hello from pk1") mypkg.Print() }
pkg2.go
package pkg2 import ( "fmt" "mymod/pkg/mypkg" ) func init() { fmt.Println("Hello everyone from pkg2 init") } func HelloFromPkg2() { fmt.Println("Hello from pk2") mypkg.Print() }
mypkg.go
package mypkg import "fmt" func init() { fmt.Println("Hello everyone from mypkg init") } var prom = 10 func Print() { fmt.Printf("address of prom inside mypkg is: %p\n", &prom) }
main.go
package main import ( "fmt" "mymod/pkg/pkg1" "mymod/pkg/pkg2" ) func init() { fmt.Println("Hello everyone from main init") } func main() { pkg1.HelloFromPkg1() pkg2.HelloFromPkg2() }
预期与实际输出
预期输出
Hello everyone from mypkg init Hello everyone from pkg1 init Hello everyone from mypkg init Hello everyone from pkg2 init Hello everyone from main init Hello from pk1 address of prom inside mypkg is: 0xfee360 (some address) Hello from pk2 address of prom inside mypkg is: 0xf321a3 (another address)
实际输出
Hello everyone from mypkg init Hello everyone from pkg1 init Hello everyone from pkg2 init Hello everyone from main init Hello from pk1 address of prom inside mypkg is: 0x8fe360 Hello from pk2 address of prom inside mypkg is: 0x8fe360
我的疑问
- 文章作者是否有误?导入的包是按模块而非按包初始化一次?
- 模块内同一包的全局变量是否始终为同一实例,仅在首次导入时初始化?
- 能否实现包每次被导入都独立初始化?比如让mypkg在pkg1和pkg2中分别初始化一次。
- 若该文章不准确,能否推荐Go包与模块的权威学习内容?
解答
文章作者没有错误,是你理解偏差了。Go语言的规则是:同一个包在整个程序运行期间仅初始化一次,不管被多少其他包导入,这个规则和模块无关,只和包的导入路径(即唯一标识)有关。只要是同一个导入路径的包,无论被多少依赖导入,都只会初始化一次。
是的,模块内同一包的全局变量是单一实例,仅在该包首次被导入时完成初始化(包括包级变量赋值和
init函数执行),后续所有使用该包的代码都会共享这一份实例,所以你看到prom的地址在pkg1和pkg2中完全相同。Go语言本身不支持让包每次被导入都独立初始化,因为包的设计初衷就是作为共享的代码单元,全局状态是单例的。如果需要独立的状态实例,你可以把包的状态封装到结构体中,让每个使用者创建结构体实例来使用,而非依赖包级全局变量。示例修改如下:
修改后的mypkg.go
package mypkg import "fmt" type MyPkg struct { prom int } // New 创建一个新的MyPkg实例 func New() *MyPkg { return &MyPkg{prom: 10} } func (m *MyPkg) Print() { fmt.Printf("address of prom inside mypkg instance is: %p\n", &m.prom) }
修改后的pkg1.go
package pkg1 import ( "fmt" "mymod/pkg/mypkg" ) var mp = mypkg.New() func init() { fmt.Println("Hello everyone from pkg1 init") } func HelloFromPkg1() { fmt.Println("Hello from pk1") mp.Print() }
修改后的pkg2.go
package pkg2 import ( "fmt" "mymod/pkg/mypkg" ) var mp = mypkg.New() func init() { fmt.Println("Hello everyone from pkg2 init") } func HelloFromPkg2() { fmt.Println("Hello from pk2") mp.Print() }
这样修改后,pkg1和pkg2会各自拥有独立的MyPkg实例,prom的地址也会不同。
- 推荐以下权威学习内容:
- 《Go语言规范》中的包和初始化章节,这是Go语言的官方语法和行为标准;
- Go官方博客中的《Using Go Modules》系列文章,详细讲解了模块的设计和使用;
- Go官方文档中的《Packages and Imports》指南,系统介绍包的导入和使用规则。
内容的提问来源于stack exchange,提问作者user20996300
相关产品推荐
相关产品推荐

