为什么在循环中创建goroutine时会发生这种情况?

Please help me. I have block codes, It uses iteration to get the elements of the map, and use this element to create a listener on a port on a Linux machine, But Its execution exceeded my expectations. code show as below:

var srvs = map[string]struct {
  id       int
  timezone string
  connCfg  string
  conn     net.Conn
}{"BrazilEastSrv": {id: 1, timezone: "Brazil/East", connCfg: "127.0.0.1:9007"},
  "AustraliaDarwinSrv": {id: 2, timezone: "Australia/Darwin", connCfg: "127.0.0.1:9008"}}

var ch1 = make(chan int, 2)

func main() {
  for k, srv := range srvs {
    go func() {
      if ln, err := net.Listen("tcp", srv.connCfg); err != nil {
        log.Fatal(fmt.Sprintf("%s : %s conn failed,", k, srv.connCfg))
      } else {
        log.Println(fmt.Sprintf("%s:%s created, port:%s listened.", k, srv.connCfg[:9], srv.connCfg[10:]))
        if conn, err := ln.Accept(); err != nil {
          log.Fatal(srv.connCfg, " conn create fatal, errmsg=", err)
        } else {
          srv.conn = conn
          defer srv.conn.Close()
          log.Println("Accepted an access request from cli:", srv.conn.RemoteAddr(), ".")
          handleMsg(srv)
        }
      }
    }()
  }
  var brazilEastSrv, australiaDarwinSrv = <-ch1
  log.Println(brazilEastSrv, australiaDarwinSrv)
}

output:

2017/12/07 11:45:48 AustraliaDarwinSrv:127.0.0.1 created, port:9008 listened.
2017/12/07 11:45:48 AustraliaDarwinSrv : 127.0.0.1:9008 conn failed,
exit status 1

Seen from the output content. It uses the last element of the iteration and reuses this element, Why does it have this problem?

But when I make some changes:

var srvs = map[string]struct {
  id       int
  timezone string
  connCfg  string
  conn     net.Conn
}{"BrazilEastSrv": {id: 1, timezone: "Brazil/East", connCfg: "127.0.0.1:9007"},
  "AustraliaDarwinSrv": {id: 2, timezone: "Australia/Darwin", connCfg: "127.0.0.1:9008"}}

var ch1 = make(chan int, 2)

func main() {
  for k, srv := range srvs {
    go func(srv struct {                                                                                                                                                                   
      id       int
      timezone string
      connCfg  string
      conn     net.Conn
    }) {
      //go func() {
      if ln, err := net.Listen("tcp", srv.connCfg); err != nil {
        log.Fatal(fmt.Sprintf("%s : %s conn failed,", k, srv.connCfg))
      } else {
        log.Println(fmt.Sprintf("%s:%s created, port:%s listened.", k, srv.connCfg[:9], srv.connCfg[10:]))
        if conn, err := ln.Accept(); err != nil {
          log.Fatal(srv.connCfg, " conn create fatal, errmsg=", err)
        } else {
          srv.conn = conn
          defer srv.conn.Close()
          log.Println("Accepted an access request from cli:", srv.conn.RemoteAddr(), ".")
          handleMsg(srv)
        }
      }
      //}()
    }(srv)
  }
  var brazilEastSrv, australiaDarwinSrv = <-ch1
  log.Println(brazilEastSrv, australiaDarwinSrv)
}

It behaves in line with my expectations, what caused that difference? Thank you for your explanation.

In the first version, your goroutine uses the variable srv (and k) which changes with each iteration of the loop, so you don't know what value your goroutine will see when it is executing. In the second version, you're passing srv as an argument to the goroutine, so the variable srv within the goroutine is no longer the same variable that is used in the loop.