我的项目是一个登录注册web服务器,由多个文件组成,并使用另一个包,其中定义了Manager
结构体。
我的文件概述:
my-package/
main.go
handlers.go
...
我有一个变量:var M *Manager
在main()
定义之前在main.go
中声明,并在main()
中赋值:
var M *Manager
func main() {
...
M = InitManager(...)
...
}
handleLogin(...)
和handleRegister(...)
是在handlers.go
中定义的使用M
变量的函数:
func handleRegister(...){
...
fmt.Println("M:", M)
M.Log1("logging informations...")
...
}
func handleLogin(...) {
...
fmt.Println("M:", M)
M.GetAccount(login)
...
}
当我转到/login
或/register
并触发相应的句柄函数时,它显示:M: <nil>
为了找出更多的东西,我修改了main()
如下所示:
var M *Manager
func main() {
...
go func() { // for debugging
for {
fmt.Println("main() goloop1: M:", M)
time.Sleep(time.Second / 2)
}
}()
M = InitManager(...)
go func() { // for debugging
for {
fmt.Println("main() goloop2: M:", M)
time.Sleep(time.Second / 2)
}
}()
...
}
和输出:
main() goloop2: M: &{...data as expected...}
main() goloop1: M: <nil>
main() goloop2: M: &{...data as expected...}
main() goloop1: M: <nil>
...
我的问题是:
- 如果一个指针给出两个值,那么指针是如何工作的?
- 如何解决我的问题,并妥善规划代码(如果这是原因),以避免这种未来?
根据Go内存模型,所提供的代码在没有适当同步的情况下写入和读取M
,这是一种数据竞争并导致未定义的行为(参见icza的注释)。
编译器假定"代码是正确同步的(这是开发人员的责任),因此它是"允许的"。假设M
在无限循环中永远不会被修改,因此它可能会反复使用给定寄存器或堆栈内存位置的副本,从而导致令人惊讶的输出。
你可以使用同步。互斥锁保护对全局*Manager
变量M
的每次访问,就像修改后的代码一样。
还要注意变量阴影!可以用M := f()
代替M = f()
,产生一个不相关的局部变量,而不影响全局变量。
我的解决方案:
我添加了init.go
:
my-package/
main.go
handlers.go
...
init.go
我把全局变量像M
和CONFIG_MAP
移动到init.go
文件:
package main
import ...
var CONFIG_MAP = LoadConfig()
var M *asrv.Manager = InitManager()
func LoadConfig() map[string]string {
// return map from 'conf.json' file
}
func InitManager() *asrv.Manager {
// return Manager configured with CONFIG_MAP
// other functions also use CONFIG_MAP that is the reason why it is global
}
func init() {
LoadTemplatesFiles() // load templates and assign value
// to a variable declared in templates.go
}
这样handlers.go
中的处理器函数(handleLoginGet
等)可以正确读取M
。
这个修复只是使我的程序工作,我仍然不知道处理这种情况的正确方法是什么,这就是为什么我在EDIT下添加了更多的信息在我的问题