Golang中goroutine代码问题排查与解决

Golang中goroutine代码问题排查与解决 以下是我的代码:

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){
	client := http.Client{
		Timeout: 5 * time.Second,
	}
	for receivedURL := range url{
		fmt.Printf("worker %d start on %s\n", id, receivedURL)
		resp, err := client.Get(receivedURL)
		if err != nil {
			resp.Body.Close()
			panic(err)
		}
		fmt.Println("response status:", resp.Status)
		scanner := bufio.NewScanner(resp.Body)
		for i := 0; scanner.Scan() && i < 5; i++ {
			fmt.Println(scanner.Text())
		}
		if err := scanner.Err(); err != nil {
			resp.Body.Close()
			panic(err)
		}
		fmt.Printf("worker %d Done with %s\n\n\n\n", id, receivedURL)
		resp.Body.Close()
		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++ {
		if <-result {
			fmt.Printf("get %d done", t)
		}
	}

}

以下是输出:它卡住了(我认为是死锁)

dth,minimum-scale=1,initial-scale=1" class="next-head"/><meta charSet="utf-8" class="next-head"/><meta charSet="utf-8" class="rennaissanceAEM64 next-head"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" class="next-head"/>
<meta name="viewport" content="initial-scale=1.0, width=device-width" class="next-head"/>
<meta name="keywords" content="att, at&amp;t, wireless, internet, tv, support, directv, phones, deals, prepaid, entertainment, bundles" class="next-head"/>
<meta name="description" content="Visit att.com to switch and save on phone plans, internet service, &amp; TV with premium entertainment! America&#x27;s best network is also the fastest." class="next-head"/>
worker 0 Done with http://att.com



response status: 200 OK
 

<!DOCTYPE html>
<html lang="en">
  <head>
worker 1 Done with http://domaintools.com



   

我无法复制完整的输出,但基本上三个工作者完成了前三个任务,但没有人能执行 result <- true,然后就出现了死锁。

这是什么原因造成的?


更多关于Golang中goroutine代码问题排查与解决的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

我们昨天讨论过这个问题。由于所有通道都是无缓冲的,当所有goroutine都开始向result发送数据时,就没有读取器可用,因为main函数仍在尝试向jobs发送数据…

更多关于Golang中goroutine代码问题排查与解决的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


问题出现在当HTTP请求发生错误时,resp.Body.Close()panic(err) 之前被调用,但 panic 会导致goroutine立即终止,而后续的 result <- true 不会执行。这会导致主goroutine在等待 result 通道的接收时发生死锁,因为发送到 result 通道的次数少于 numberOfTasks

以下是修复后的代码:

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) {
	client := http.Client{
		Timeout: 5 * time.Second,
	}
	for receivedURL := range url {
		fmt.Printf("worker %d start on %s\n", id, receivedURL)
		resp, err := client.Get(receivedURL)
		if err != nil {
			fmt.Printf("worker %d error on %s: %v\n", id, receivedURL, err)
			result <- false
			continue
		}
		fmt.Println("response status:", resp.Status)
		scanner := bufio.NewScanner(resp.Body)
		for i := 0; scanner.Scan() && i < 5; i++ {
			fmt.Println(scanner.Text())
		}
		if err := scanner.Err(); err != nil {
			fmt.Printf("worker %d scan error on %s: %v\n", id, receivedURL, err)
			resp.Body.Close()
			result <- false
			continue
		}
		fmt.Printf("worker %d Done with %s\n\n", id, receivedURL)
		resp.Body.Close()
		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
		fmt.Printf("get %d done\n", t)
	}
}

主要修改:

  1. panic(err) 替换为错误处理和向 result 通道发送 false
  2. 使用 continue 跳过当前迭代的剩余部分,确保每个URL处理都会向 result 通道发送一个值
  3. 在错误情况下也向 result 通道发送值,保证主goroutine不会死锁

这样无论请求成功还是失败,每个worker都会为每个处理的URL向 result 通道发送一个值,主goroutine能够接收到所有 numberOfTasks 个结果。

回到顶部