相同的指针,不同的值,在多文件主包中。

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

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>
...

我的问题是:

  1. 如果一个指针提供了两个值,指针是如何工作的?
  2. 如何修复我的问题并正确规划代码(如果这是问题的原因),以避免将来出现这种情况?
英文:

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(&quot;M:&quot;, M)
  M.Log1(&quot;logging informations...&quot;)
  ...
}


func handleLogin(...) {
  ...
  fmt.Println(&quot;M:&quot;, M)
  M.GetAccount(login)
  ...
}

When I go to /loginor /register and the appropriate handle function is triggered it displays: M: &lt;nil&gt;

To find out something more I modified main() as shown below:

var M *Manager

func main() {
  ...
  go func() {  // for debugging
    for {
      fmt.Println(&quot;main() goloop1: M:&quot;, M)
      time.Sleep(time.Second / 2)
    }
  }()

  M = InitManager(...)

  go func() {  // for debugging
    for {
      fmt.Println(&quot;main() goloop2: M:&quot;, M)
      time.Sleep(time.Second / 2)
    }
  }()
  ...
}

and the output:

main() goloop2: M: &amp;{...data as expected...}
main() goloop1: M: &lt;nil&gt;
main() goloop2: M: &amp;{...data as expected...}
main() goloop1: M: &lt;nil&gt;
...

My question are:

  1. How do pointers work then if one pointer gives out two values?
  2. 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

我将像MCONFIG_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 &#39;conf.json&#39; 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.

huangapple
  • 本文由 发表于 2021年8月2日 21:04:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/68622096.html
匿名

发表评论

匿名网友

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

确定