在多个goroutine中使用Scanf会导致意外的结果。

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

Scanf in multiple goroutines giving unexpected results

问题

我只返回翻译好的部分,以下是你要翻译的内容:

我只是在尝试使用golang编程语言。我遇到了一个有趣的结果。这是我的代码。

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)
}

我输入了以下内容。

beat
it

我期望的结果是

it beat

或者

beat it

但是我得到了

eat bit

请问有人可以帮我弄清楚为什么会这样吗?

英文:

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?

答案1

得分: 4

fmt.Scanf不是一个原子操作。这是它的实现代码:http://golang.org/src/pkg/fmt/scan.go#L1115

没有信号量,也没有阻止两个并行执行的机制。因此,实际上这两个执行是并行的,由于没有缓冲,任何字节的读取都是一个IO操作,因此是Go调度器切换goroutine的理想时机。

英文:

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.

答案2

得分: 4

问题在于你正在跨多个goroutine共享单个资源(stdin字节流)。

每个goroutine可能在不确定的时间点被创建。例如:

  1. 第一个goroutine读取所有的stdin,然后启动第二个goroutine。
  2. 第一个goroutine读取所有的stdin,然后启动第二个goroutine。
  3. 第一个goroutine在读取时被阻塞,然后启动第二个goroutine读取一个字符,然后重新启动第一个goroutine。
  4. ...以此类推...

在大多数情况下,只需要使用一个goroutine来访问线性资源(如字节流),并将一个通道连接到它,然后生成多个消费者来监听该通道。

例如:

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()
	}()
	// 多个消费者
	for i := 0; i < 5; i += 1 {
		go func() {
			for word := range words {
				fmt.Printf("%s\n", word)
			}
		}()
	}
	wg.Wait()
}
英文:

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 (
	&quot;fmt&quot;
	&quot;io&quot;
	&quot;sync&quot;
)

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

huangapple
  • 本文由 发表于 2014年5月28日 17:11:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/23907364.html
匿名

发表评论

匿名网友

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

确定