Golang中Channels的使用帮助

Golang中Channels的使用帮助 大家好, 我正在尝试学习通道,当我运行代码时,得到以下输出,第一次运行后变为大写。在playground中我得到的是全小写。运行版本为v1.10。 提前感谢。

google
ALTAVISTA
ALPHABET
BING
YAHOO
func main() {
	names := []string{
		"yahoo", "google", "altavista", "alphabet", "bing",
	}

	n := make(chan string, len(names))


	for _, name := range names {
		n <- name
		go ToUpper(n)
	}

	for i := 0; i < len(names); i++ {
		fmt.Println(<-n)
	}

	close(n)
}

func ToUpper(c chan string) {

	u := strings.ToUpper(<-c)
	c <- u
}

更多关于Golang中Channels的使用帮助的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

谢谢


更多关于Golang中Channels的使用帮助的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你没有等待 goroutine 执行完成。最好使用无缓冲通道和等待组。最简单但不理想的解决方案是在 main() 函数末尾添加休眠时间。

另外,通道应当仅用于读取或写入,不要同时进行两种操作。

为输入和结果使用独立的通道。

在你的代码中,ToUpper协程和主协程中的fmt.Println循环从同一个通道读取数据,该通道同时包含输入数据和ToUpper()的输出结果。由于协程的运行顺序没有明确定义,因此结果是不确定的。

Playground首先运行fmt.Println循环,这就是你得到未处理输入作为输出的原因。当ToUpper协程运行时,通道已经为空。(除此之外,当主协程结束时,所有其他协程会立即停止,因此ToUpper协程可能永远不会运行。)

在你的本地机器上,fmt.Println()循环似乎在第一个ToUpper协程启动前至少运行了一次,这就是为什么你看到第一个输出未经处理的原因。

为输入值和结果值使用独立的通道应该能解决这个问题。同时要确保主协程等待其他协程完成。(例如参考sync/WaitGroup。)

感谢大家的建议。 我已经通过这个示例解决了问题。

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
)

func getLength(url string) (int, error) {
	resp, err := http.Get(url)
	if err != nil {
		return 0, nil
	}
	defer resp.Body.Close()

	pageByte, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return 0, nil
	}

	return len(pageByte), nil

}

func worker(urlCh, sizeCh chan string, id int) {
	for {
		url := <-urlCh
		psize, err := getLength(url)
		if err != nil {
			os.Exit(1)
		}
		sizeCh <- fmt.Sprintf("%s length is %d (%d)", url, psize, id)
	}
}

func generator(url string, urlCh chan string) {
	urlCh <- url
}

func main() {
	// url := "http://www.google.ca"
	urls := []string{"http://www.golang.org", "http://www.google.ca",
		"http://www.bing.com", "http://www.yahoo.ca", "http://www.toronto.ca"}

	sizeCh := make(chan string)
	urlCh := make(chan string)

	for i := 0; i < 10; i++ {
		go worker(urlCh, sizeCh, i)
	}

	for _, url := range urls {
		go generator(url, urlCh)
	}

	for i := 0; i < len(urls); i++ {
		fmt.Printf("%s\n", <-sizeCh)
	}
}

在您的代码中,通道 n 是一个带缓冲的通道,容量为 len(names)(即5)。问题在于您在主 goroutine 中向通道发送所有字符串,然后启动 goroutine 来从通道接收并转换为大写,但 goroutine 的执行顺序是不确定的。这可能导致某些字符串在第一次运行后显示为大写,而其他运行可能不同,因为 goroutine 的调度顺序可能变化。

具体来说,在循环中:

  • 您将每个 name 发送到通道 n
  • 然后启动一个 goroutine 调用 ToUpper,它从通道接收一个值,转换为大写,并发送回通道。

由于通道有缓冲,主 goroutine 可以立即发送所有值而不阻塞。然后,goroutine 并发地从通道接收,但它们的执行顺序会影响输出。例如,如果某个 goroutine 在另一个 goroutine 发送回大写值之前从通道接收,您可能会看到小写输出。在 Go Playground 上,由于环境限制,goroutine 的调度可能更一致,导致全小写。

要确保所有字符串都转换为大写,您应该重构代码,使每个字符串的处理独立,并避免在同一个通道上混合发送和接收。以下是修改后的示例,使用无缓冲通道和同步方式:

package main

import (
	"fmt"
	"strings"
)

func main() {
	names := []string{
		"yahoo", "google", "altavista", "alphabet", "bing",
	}

	n := make(chan string) // 无缓冲通道

	// 启动 goroutine 来处理每个名称
	for _, name := range names {
		go func(name string) {
			u := strings.ToUpper(name)
			n <- u
		}(name)
	}

	// 从通道接收所有结果
	for i := 0; i < len(names); i++ {
		fmt.Println(<-n)
	}

	close(n)
}

在这个版本中:

  • 使用无缓冲通道 n
  • 对每个 name 启动一个 goroutine,直接在 goroutine 中转换为大写并发送到通道。
  • 主 goroutine 从通道接收所有结果。

输出应该始终是全大写的,例如:

ALTAVISTA
ALPHABET
BING
YAHOO
GOOGLE

注意:输出顺序可能因 goroutine 完成时间而异,但所有字符串都会是大写。如果您需要保持原始顺序,可以使用带索引的 goroutine 和结果切片。

回到顶部