Golang中goroutine和channel的使用问题探讨

Golang中goroutine和channel的使用问题探讨 程序在运行这段代码时卡住了。

package main

import (
	_ "bufio"
	"fmt"
	_ "net/http"
	"time"
)

const (
	numberOfWorkers int = 3
	numberOfTasks int = 5
)

func worker(id int, url chan string, result chan bool){
	for receivedURL := range url {
		fmt.Printf("worker %d start job %s\n", id, receivedURL)
		time.Sleep(1 * time.Second)
		fmt.Printf("worker %d finish job %s\n", id, receivedURL)
		result <- true
	}
}

func main() {
	url := make(chan string)
	result := make(chan bool)
	urls := []string{
		"http://gobyexample.com",
		"http://att.com",
		"http://domaintools.com",
		"http://microsoft.com",
		"http://google.com",
	}
	for i := 0; i < numberOfWorkers; i++ {
		go worker(i, url, result)
	}
	for j := 0; j < numberOfTasks; j++ {
		url <- urls[j]
	}
	close(url)

	for t := 0; t < numberOfTasks; t++ {
		//<-result
		if <-result {
			fmt.Printf("get %d done\n", t)
		}
	}

}

以下是输出:

(base) zwang-mac:cmd1 zwang$ go run httpRoutine5-1.go
worker 2 start job http://gobyexample.com
worker 1 start job http://domaintools.com
worker 0 start job http://att.com
worker 2 finish job http://gobyexample.com
worker 0 finish job http://att.com
worker 1 finish job http://domaintools.com

程序在那里卡住了,虽然我没有收到死锁的错误信息,但看起来像是死锁了。

当我将两个无缓冲通道改为如下所示的缓冲通道时,程序成功运行完成。我想知道第一个版本有什么问题?

package main

import (
	_ "bufio"
	"fmt"
	_ "net/http"
	"time"
)

const (
	numberOfWorkers int = 3
	numberOfTasks int = 5
)

func worker(id int, url chan string, result chan bool){
	for receivedURL := range url {
		fmt.Printf("worker %d start job %s\n", id, receivedURL)
		time.Sleep(1 * time.Second)
		fmt.Printf("worker %d finish job %s\n", id, receivedURL)
		result <- true
	}
}

func main() {
	url := make(chan string, 100)
	result := make(chan bool, 100)
	urls := []string{
		"http://gobyexample.com",
		"http://att.com",
		"http://domaintools.com",
		"http://microsoft.com",
		"http://google.com",
	}
	for i := 0; i < numberOfWorkers; i++ {
		go worker(i, url, result)
	}
	for j := 0; j < numberOfTasks; j++ {
		url <- urls[j]
	}
	close(url)

	for t := 0; t < numberOfTasks; t++ {
		//<-result
		if <-result {
			fmt.Printf("get %d done\n", t)
		}
	}

}

更多关于Golang中goroutine和channel的使用问题探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

由于所有通道都是无缓冲的,主 goroutine 最终会尝试发送下一个工作项,而此时所有工作协程都在尝试将它们的 result 发送回来,这些结果在所有工作项发送完成之前都不会被读取。

更多关于Golang中goroutine和channel的使用问题探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


问题出现在第一个版本中,当使用无缓冲通道时,程序确实会发生死锁。让我分析一下具体原因:

问题分析:

在第一个版本中,result 通道是无缓冲的。当所有worker完成前3个任务后,它们会尝试向result通道发送结果,但此时主goroutine还没有开始接收这些结果。由于通道是无缓冲的,发送操作会阻塞,直到有接收者准备好。

具体来说:

  • 有3个worker处理5个任务
  • 前3个任务被worker处理后,worker尝试发送结果到result通道
  • 但主goroutine此时还在等待result通道的数据
  • 由于没有接收者,发送操作被阻塞,worker无法继续处理剩余的任务

解决方案:

正确的做法是确保接收结果的操作在发送操作之前开始,或者使用缓冲通道。以下是修复后的代码:

package main

import (
	"fmt"
	"time"
)

const (
	numberOfWorkers int = 3
	numberOfTasks   int = 5
)

func worker(id int, url chan string, result chan bool) {
	for receivedURL := range url {
		fmt.Printf("worker %d start job %s\n", id, receivedURL)
		time.Sleep(1 * time.Second)
		fmt.Printf("worker %d finish job %s\n", id, receivedURL)
		result <- true
	}
}

func main() {
	url := make(chan string)
	result := make(chan bool)
	urls := []string{
		"http://gobyexample.com",
		"http://att.com",
		"http://domaintools.com",
		"http://microsoft.com",
		"http://google.com",
	}
	
	// 先启动worker
	for i := 0; i < numberOfWorkers; i++ {
		go worker(i, url, result)
	}
	
	// 启动一个单独的goroutine来发送URLs
	go func() {
		for j := 0; j < numberOfTasks; j++ {
			url <- urls[j]
		}
		close(url)
	}()
	
	// 主goroutine接收结果
	for t := 0; t < numberOfTasks; t++ {
		if <-result {
			fmt.Printf("get %d done\n", t)
		}
	}
}

另一种解决方案是使用sync.WaitGroup

package main

import (
	"fmt"
	"sync"
	"time"
)

const (
	numberOfWorkers int = 3
	numberOfTasks   int = 5
)

func worker(id int, url chan string, wg *sync.WaitGroup) {
	defer wg.Done()
	for receivedURL := range url {
		fmt.Printf("worker %d start job %s\n", id, receivedURL)
		time.Sleep(1 * time.Second)
		fmt.Printf("worker %d finish job %s\n", id, receivedURL)
	}
}

func main() {
	url := make(chan string)
	var wg sync.WaitGroup
	urls := []string{
		"http://gobyexample.com",
		"http://att.com",
		"http://domaintools.com",
		"http://microsoft.com",
		"http://google.com",
	}
	
	wg.Add(numberOfTasks)
	
	for i := 0; i < numberOfWorkers; i++ {
		go worker(i, url, &wg)
	}
	
	for j := 0; j < numberOfTasks; j++ {
		url <- urls[j]
	}
	close(url)
	
	wg.Wait()
	fmt.Println("All tasks completed")
}

问题的根本原因是无缓冲通道的同步特性导致了goroutine间的执行顺序问题。使用缓冲通道或者重新组织goroutine的执行顺序都可以解决这个问题。

回到顶部