How does the golang package import flow work and is there a way to initialize one package multiple times within the same module?

huangapple go评论85阅读模式
英文:

How does the golang package import flow work and is there a way to initialize one package multiple times within the same module?

问题

我是新手学习golang,目前正在研究包和导入的工作原理。我对这个导入有几个问题。我正在阅读这篇文章(它有超过7k个赞,所以我猜它是准确的+这是我在输入golang packages时谷歌给我的第一个结果)。

为了解释我不理解的地方,我首先会写出项目结构和所有文件内容。

pkg
├── mypkg
|  └── mypkg.go
|
├── pkg1
|  └── pkg1.go
|
└── pkg2
   └── pkg2.go
go.mod
main.go

如你所见,我有一个包含3个包(包括main)的模块。所有包的内容如下所示。

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的内容

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()
}

所以,main.go导入了pkg1pkg2,而pkg1pkg2都导入了mypkg。我参考的文章中提到了以下内容(已加粗):

> 最重要的是要记住,每个导入的包只会初始化一次

在这个前提下,我期望程序的输出如下所示:

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(某个地址)
Hello from pk2
address of prom inside mypkg is: 0xf321a3(另一个地址)

我期望按照以下步骤进行:

  • 进入main包(1)
  • 初始化pkg1包(2)
    • 初始化mypkg包(2.1)
      • 初始化mypkg中的所有全局变量 - 在我的例子中是prom(2.1.1)
      • 调用mypkg的init函数(2.1.2)
    • 调用pkg1的init函数(2.2)
  • 初始化pkg2包(3)
    • 初始化mypkg包(3.1)
      • 初始化mypkg中的所有全局变量 - 在我的例子中是prom(3.1.1)
      • 调用mypkg的init函数(3.1.2)
    • 调用pkg2的init函数(3.2)
  • 初始化main包(4)
  • 调用main的init函数(5)
  • 调用main包的main函数(6)

但实际输出如下所示:

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只在第一次导入时初始化一次?而且全局变量prom的地址在pkg1和pkg2中是相同的(在这种情况下是0x8fe360)。

所以我的问题是:

  1. 文章的作者是否犯了一个错误?导入的包不是每个包只初始化一次,而是每个模块只初始化一次
  2. 这是否意味着一个包中的全局变量在整个模块中始终是相同的(相同的地址),无论从哪里和多少次导入?我是说它们只会在第一次导入时初始化一次
  3. 是否有办法使我的“流程”起作用?我的意思是每次导入都有独立的一个包初始化?在我的例子中,这意味着mypkgpkg1中初始化一次,在pkg2中再初始化一次。
  4. 是否有关于go中包和模块的好文章,如果我读的那篇不正确的话?

我知道对于一些人来说,这些问题是关于golang的一些基础知识,但对于我这样的初学者来说,这引起了一些误解。尤其是因为程序的结果与在谷歌搜索时出现的第一篇文章的作者所写的不符。任何帮助都将不胜感激。祝一切顺利!

英文:

I'm new to golang and I'm currently researching how packages and importing them works. I have a few questions about this import. I am reading this article (it has more than 7k likes so I guess it's accurate + it's first what google gives me when I type golang packages).

To explain what I don't understand, I will first write what the project structure looks like and what the contents of all files are.

pkg
├── mypkg
|  └── mypkg.go
|
├── pkg1
|  └── pkg1.go
|
└── pkg2
   └── pkg2.go
go.mod
main.go

As you can see, I have module with 3 packages (4 including main). The contents of all packages are listed below.

Content of the 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()
}

Content of the 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()
}

Content of the mypkg:

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)
}

Content of the 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()
}

So, main.go includes pkg1 and pkg2 and both pkg and pkg2 include mypkg. The article I refer to states the following (it's bolded):

> The main thing to remember is, an imported package is initialized only once per package.

Having that in mind, I am expecting something like this to be the output of my program:

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)

My expectation is that the following steps will be followed:

  • entered the main package (1)
  • pkg1 package is initialized (2)
    • mypkg package is initialized (2.1)
      • all global variables inside mypkg is initialized -prom in my case (2.1.1)
      • init function of mypkg is called (2.1.2)
    • init function of pkg1 is called (2.2)
  • pkg2 package is initialized (3)
    • mypkg package is initialized (3.1)
      • all global variables inside mypkg is initialized -prom in my case (3.1.1)
      • init function of mypkg is called (3.1.2)
    • init function of pkg2 is called (3.2)
  • main package is initialized (4)
  • init function of main is called (5)
  • main function of main package is called (6)

Instead of it, I get the following output:

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

It looks like mypkg is initialized only once during the first import?! Also, the address of the global variable prom is the same in pkg1 and pkg2 (in this case it's 0x8fe360).

So my questions are:

  1. Does the author of the article made a misstake? Imported packages are not initialized only once per package, but once per module?
  2. Does this mean that global variables from one package will always be the same throughout the module (same address) no matter how many times and from where the package is included? I mean they will only be initialized once during first import?
  3. Is there some way to make my "flow" work? By that I mean there is independent initialization of one package per each import? In my example this means that mypkg is initialized once in pkg1 and another time in pkg2.
  4. Does anyone have a good article on packages and modules in go if the one I read is not correct?

I know for some these questions are some basics about golang, but for me as a beginner this caused some misunderstandings. All the more so since the result of the program's work does not match what the author of the article that comes up first during a Google search wrote. Any help is welcome. All the best!

答案1

得分: 1

程序中包含的包是一个集合,它是从main开始的所有导入包的传递闭包。也就是说:

  • 它是一个集合。每个导入的包只包含一次。这意味着,如果你在一个包中定义了一个变量,那个变量只会出现一次。
  • 所有导入的包以及它们递归导入的包都包含在最终的二进制文件中。

至于初始化:你的步骤是正确的,只是mypkg只会被初始化一次。在二进制文件中不会有多个mypkg的副本。

英文:

The packages included in a program is a set that is the transitive closure of all imported packages starting from main. That is:

  • It is a set. Every imported package is included only once. That means, if you have a variable defined in a package, that variable only appears once.
  • All imported packages, and the packages they import, recursively, are included in the final binary.

As for initialization: your steps are correct, except that mypkg is initialized only once. There are not multiple copies of mypkg in the binary.

huangapple
  • 本文由 发表于 2023年1月13日 10:26:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/75104286.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定