Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scanf in multiple goroutines giving unexpected results

Tags:

go

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?

like image 758
Varun Vasan V Avatar asked May 28 '14 09:05

Varun Vasan V


2 Answers

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.

like image 72
Denys Séguret Avatar answered Sep 28 '22 11:09

Denys Séguret


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()
}
like image 31
fabrizioM Avatar answered Sep 28 '22 10:09

fabrizioM