GOMAXPROCS = 1时的数据争用

I'm trying to understand one of the Golang typical data races where access to an unprotected global variable from multiple goroutines may cause a race condition:

var service map[string]net.Addr

func RegisterService(name string, addr net.Addr) {
  service[name] = addr
}

func LookupService(name string) net.Addr {
  return service[name]
}

It goes on to say that we can solve this by protecting it with a mutex:

var (
  service   map[string]net.Addr
  serviceMu sync.Mutex
)

func RegisterService(name string, addr net.Addr) {
  serviceMu.Lock()
  defer serviceMu.Unlock()
  service[name] = addr
}

func LookupService(name string) net.Addr {
  serviceMu.Lock()
  defer serviceMu.Unlock()
  return service[name]
}

So far, so good. What's confusing me is this:

The accepted answer to this question suggests that a CPU-bound goroutine will any starve other goroutines that have been multiplexed onto the same OS thread (unless we explicitly yield with runtime.Gosched()). Which makes sense.

To me, the RegisterService() and LookupService() functions above look to be CPU bound, as there's no IO and no yield. Is this correct?

If it is, and if GOMAXPROCS is set to 1, then is a mutex in the above example still strictly necessary? Doesn't the fact that the goroutines are CPU-bound at the point where race conditions might occur take care of it?

Even if it does, I presume that in real life using a mutex is here is still a good idea as we may not be able to guarantee what GOMAXPROCS is set to. Are there another reasons?

As FUZxxl and Nick Craig-Wood noted, current behavior of goroutines is implementation-specific. So, maybe, read or write to map can yield. Considering that maps are not thread safe, mutex or another syncronization is required for correct concurrent access.

As alternative to mutex, you can spawn a goroutine, that performs all operations on your map and talk to it with channels:

type writereq struct {
    key string
    value net.Addr
    reply chan struct{}
}

type readreq struct {
    key string
    reply chan net.Addr
}

var service map[string]net.Addr
var reads = make(chan readreq)
var writes = make(chan writereq)

func RegisterService(name string, addr net.Addr) {
    w := writereq{name, addr, make(chan struct{})}
    writes <- w
    return <-w.reply // return after registration confirmation
}

func LookupService(name string) net.Addr {
    r := readreq{name, make(chan net.Addr)}
    reads <- r
    return <-r.reply
}

func serveRegistry() {
    for {
        select {
        case r := <-reads:
            r.reply <- service[r.name]
        case w := <-writes:
            service[w.name] = w.addr
            w.reply <- struct{}
        }
    }
}