英文:
Same pointer different values, in multi file main package
问题
我的项目是一个登录注册的Web服务器,由多个文件组成,并使用另一个包,其中定义了Manager
结构体。
我的文件概述:
my-package/
main.go
handlers.go
...
我在main.go
中声明了一个变量:var M *Manager
,在main()
函数定义之前,并在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>
...
我的问题是:
- 如果一个指针提供了两个值,指针是如何工作的?
- 如何修复我的问题并正确规划代码(如果这是问题的原因),以避免将来出现这种情况?
英文:
My project is a login-register web server that consists of multiple files and uses another package in which the Manager
struct is defined.
Overview of my files:
my-package/
main.go
handlers.go
...
I have a variable: var M *Manager
declared in main.go
before definition of main()
and it is assigned inside main()
:
var M *Manager
func main() {
...
M = InitManager(...)
...
}
handleLogin(...)
and handleRegister(...)
are functions defined in handlers.go
that use the M
variable:
func handleRegister(...){
...
fmt.Println("M:", M)
M.Log1("logging informations...")
...
}
func handleLogin(...) {
...
fmt.Println("M:", M)
M.GetAccount(login)
...
}
When I go to /login
or /register
and the appropriate handle function is triggered it displays: M: <nil>
To find out something more I modified main()
as shown below:
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)
}
}()
...
}
and the output:
main() goloop2: M: &{...data as expected...}
main() goloop1: M: <nil>
main() goloop2: M: &{...data as expected...}
main() goloop1: M: <nil>
...
My question are:
- How do pointers work then if one pointer gives out two values?
- How to fix my issue and properly plan code (if that was the cause) to avoid this in future?
答案1
得分: 1
根据Go内存模型,提供的代码在没有适当同步的情况下写入和读取M
,这是数据竞争,并导致未定义的行为(请参阅icza的评论)。
编译器“假设”代码已经正确同步(这是开发人员的责任),因此它“允许”假设M
在无限循环中从未被修改过,因此它可以在给定的寄存器或堆栈内存位置上重复使用副本,导致出乎意料的输出。
您可以使用sync.Mutex来保护对全局*Manager
变量M
的每次访问,就像在这个修改后的代码中所示。
还要注意变量遮蔽!可能会写成M := f()
而不是M = f()
,这会产生一个不相关的局部变量,不会影响全局变量。
英文:
Per the Go Memory Model, the provided code writes and reads M
without proper synchronization, which is a data race and leads to undefined behavior (see icza's comment).
The compiler "assumes" that the code is properly synchronized (this is the responsibility of the developer) and so it is "allowed" to assume that M
is never modified inside the infinite loops, so it may use a copy in a given register or stack memory location over and over, leading to the surprising output.
You may use a sync.Mutex to protect every access to the global *Manager
variable M
, as in this modified code.
Also beware of variable shadowing! It is possible to write M := f()
instead of M = f()
, resulting in an unrelated local variable, not affecting the global variable.
答案2
得分: 0
我的解决方案:
我添加了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 {
// 从'conf.json'文件中返回map
}
func InitManager() *asrv.Manager {
// 使用CONFIG_MAP配置Manager并返回
// 其他函数也使用CONFIG_MAP,这就是为什么它是全局的原因
}
func init() {
LoadTemplatesFiles() // 加载模板并将值分配给在templates.go中声明的变量
}
这样,handlers.go
中的处理函数(如handleLoginGet
等)就可以正确读取M
了。
这个修复方法让我的程序正常工作了,但我仍然不知道处理这种情况的正确方式,这就是为什么我在我的问题下面的EDIT部分添加了更多信息的原因。
英文:
My solution:
I have added init.go
:
my-package/
main.go
handlers.go
...
init.go
I moved global variables like M
and CONFIG_MAP
into the init.go
file:
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
}
This way handler functions (handleLoginGet
etc.) in handlers.go
could properly read M
.
This fix just made my program work and I still don't know what is the proper way of handling this type of situation, that is why I added more info under EDIT in my question.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论