Varun Vasan V
Varun Vasan V

Reputation: 193

Scanf in multiple goroutines giving unexpected results

I was simply experimenting in golang. I came across an interesting result. This is my code.

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    var str1, str2 string
    wg.Add(2)
    go func() {
        fmt.Scanf("%s", &str1)
        wg.Done()
    }()
    go func() {
        fmt.Scanf("%s", &str2)
        wg.Done()
    }()
    wg.Wait()
    fmt.Printf("%s %s\n", str1, str2)
}

I gave the following input.

beat
it

I was expecting the result to be either

it beat

or

beat it

But I got.

eat bit

Can any one please help me figure out why it is so?

Upvotes: 5

Views: 612

Answers (2)

fabmilo
fabmilo

Reputation: 48330

The problem is that you are sharing a single resource (the stdin byte stream) across multiple goroutines.

Each goroutine could be spawn at different non-deterministic times. i.e:

  1. first goroutine 1 read all stdin, then start goroutine 2
  2. first goroutine 2 read all stdin, then start goroutine 1
  3. first goroutine 1 block on read, then start goroutine 2 read one char and then restart goroutine 1
  4. ... and so on and on ...

In most cases is enough to use only one goroutine to access a linear resource as a byte stream and attach a channel to it and then spawn multiple consumers that listen to that channel.

For example:

package main

import (
    "fmt"
    "io"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    words := make(chan string, 10)
    wg.Add(1)
    go func() {
        for {
            var buff string
            _, err := fmt.Scanf("%s", &buff)
            if err != nil {
                if err != io.EOF {
                    fmt.Println("Error: ", err)
                }
                break
            }
            words <- buff
        }
        close(words)
        wg.Done()
    }()
    // Multiple consumers
    for i := 0; i < 5; i += 1 {
        go func() {
            for word := range words {
                fmt.Printf("%s\n", word)
            }
        }()
    }
    wg.Wait()
}

Upvotes: 4

Denys S&#233;guret
Denys S&#233;guret

Reputation: 382394

fmt.Scanf isn't an atomic operation. Here's the implementation : http://golang.org/src/pkg/fmt/scan.go#L1115

There's no semaphor, nothing preventing two parallel executions. So what happens is simply that the executions are really parallel, and as there's no buffering, any byte reading is an IO operation and thus a perfect time for the go scheduler to change goroutine.

Upvotes: 4

Related Questions