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
由于所有通道都是无缓冲的,主 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的执行顺序都可以解决这个问题。

