Golang代码中的这个问题是什么原因导致的
Golang代码中的这个问题是什么原因导致的 我正在学习使用通道和go http.get的goroutine,在下面的代码运行时,它会卡住,没有任何错误或警告。这里有什么问题?
package main
import (
"bufio"
"fmt"
"net/http"
"sync"
)
var wg sync.WaitGroup
func worker(url <-chan string){
defer wg.Done()
resp, err := http.Get(<-url)
if err != nil {
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 {
panic(err)
}
}
func main() {
wg.Add(3)
url := make(chan string)
urls := []string{
"http://gobyexample.com",
"http://att.com",
"http://domaintools.com",
"http://microsoft.com",
"http://google.com",
}
for i := 0; i < 3; i++ {
url <- urls[i]
go worker(url)
}
wg.Wait()
}
更多关于Golang代码中的这个问题是什么原因导致的的实战教程也可以访问 https://www.itying.com/category-94-b0.html
jameswang2015:
我这里已经很晚了,明天再执行第三步。
收到。
更多关于Golang代码中的这个问题是什么原因导致的的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
我已经成功执行了第一步,运行正常。我的 Go 版本是 go1.12.9
[下一页 →](/t/whats-the-issue-with-this-code/15642?page=2)
对于第二步,我认为defer received.Body.Close()这样写没问题,因为它不在for循环中。
现在时间很晚了,我明天再完成第三步。
url 是无缓冲的,因此由于通道上没有接收器,它无法越过 url <- urls[i]。
func main() {
fmt.Println("hello world")
}
func worker(url <-chan string) 用于定义一个仅能接收值的通道,这类似于定义一种更严格的通道类型。
我不太清楚内部超时的使用(为什么在这种情况下需要它?)以及并发规划。你能为此举个例子吗?
func main() {
fmt.Println("hello world")
}
jameswang2015:
我已经成功执行了第一步。
这很可能与我的 golang-linters 有关。请注意这个警告:go - 如果不关闭 response.Body 会发生什么? - Stack Overflow
jameswang2015:
这是更新后的第一步:
很好。
现在你对第二步有信心了吗?你可以继续第三步了。先制定一个计划。我们可以在编写代码之前先审查一下。
注意:
如果你想要更多挑战,可以在第三步之前尝试将所有fmt打印抽象成一个函数。这会增加现有第二步的复杂度,但能换取一些实际的调试经验,并且需要更长的学习时间。 😄
引用 hollowaykeanho:
- 在不使用并发的情况下执行
http.Client.Get。首先在main函数中完成所有操作。- 仍然仅在
main函数中,将http.Client.Get抽象到其自身的函数中。- 规划你的并发(为什么我会遇到间歇性的死锁错误?)。
如果你感到困惑,尝试一次只做一步并发布你的代码。这次,将由你来编写代码。
在
for循环中使用 defer 是否正确?我收到“可能存在资源泄漏”的警告。
在 for 循环中使用 defer 没有意义。你需要在不使用 defer 关键字的情况下,手动使用 received.Body.Close() 来关闭它。例如:
- 在所有
panic(err)之前 - 在第一个循环(
for i := 0; i < len(urls); i++)的末尾,再执行一次
如何找出?
查找所有循环的退出点(例如 panic 是一个退出点),循环结束是另一个退出点。
jameswang2015:
这里是第一步
你运行过了吗?我遇到了:
main.go:22:30: bodyclose: response body must be closed (bodyclose)
received, err := client.Get(urls[i])
你需要关闭 Body。否则会导致资源泄漏,客户端可能无法为后续的"keep-alive"请求复用与服务器的持久 TCP 连接。要关闭它,请在 get 请求后使用以下代码:
defer received.Body.Close()
注意: 你已经进行到第二步了。
在进入第三步之前,请确保尽可能清晰地进行所有重构。
jameswang2015: 我不太清楚如何使用内部超时(为什么在这种情况下需要它?)以及规划并发。你能举个例子吗?
假设服务器处理一个请求需要120秒(1分钟),而你的应用程序每个请求最多只允许60秒,那么你将会把所有时间预算都耗费在等待上。
jameswang2015:
func worker(url <-chan string)是定义一个仅接收值的通道,这就像定义一个更严格的通道类型。
这就是为什么我现在不推荐使用它(除非你已经精通并发并且对逻辑有很强的控制能力)。最好传入一个通道,这样你就可以完全控制等待/读取操作。
jameswang2015: 规划并发。你能举个例子吗?
你查看过那个链接吗?里面有一个关于如何规划并发的清晰示例。如果你能清晰地规划并发上下文,就完全不会使用全局变量,特别是互斥锁。
以下是第二步:
package main
import (
"bufio"
"fmt"
"net/http"
"time"
)
func httpCall(url string) {
client := http.Client{
Timeout: 5 * time.Second,
}
received, err := client.Get(url)
if err != nil {
panic(err)
}
defer received.Body.Close()
fmt.Println("status: ", received.StatusCode)
scanner := bufio.NewScanner(received.Body)
for i := 0; scanner.Scan() && i < 5; i++ {
fmt.Println(scanner.Text())
}
if err := scanner.Err(); err != nil {
panic(err)
}
}
func main() {
urls := []string{
"http://gobyexample.com",
"http://att.com",
"http://domaintools.com",
"http://microsoft.com",
"http://google.com",
}
for i := 0; i < len(urls); i++ {
httpCall(urls[i])
}
}
以下是更新后的第一步内容:
package main
import (
"bufio"
"fmt"
"net/http"
"time"
)
func main() {
urls := []string{
"http://gobyexample.com",
"http://att.com",
"http://domaintools.com",
"http://microsoft.com",
"http://google.com",
}
client := http.Client{
Timeout: 5 * time.Second,
}
for i := 0; i < len(urls); i++ {
received, err := client.Get(urls[i])
if err != nil {
received.Body.Close()
panic(err)
}
fmt.Println("status: ", received.Status)
scanner := bufio.NewScanner(received.Body)
for i := 0; scanner.Scan() && i < 5; i++ {
fmt.Println(scanner.Text())
}
if err := scanner.Err(); err != nil {
received.Body.Close()
panic(err)
}
received.Body.Close()
}
}
以下是更新后的步骤1:
package main
import (
"bufio"
"fmt"
"net/http"
"time"
)
func main() {
urls := []string{
"http://gobyexample.com",
"http://att.com",
"http://domaintools.com",
"http://microsoft.com",
"http://google.com",
}
client := http.Client{
Timeout: 5 * time.Second,
}
for i := 0; i < len(urls); i++ {
received, err := client.Get(urls[i])
if err != nil {
panic(err)
}
defer received.Body.Close()
fmt.Println("status: ", received.Status)
scanner := bufio.NewScanner(received.Body)
for i := 0; scanner.Scan() && i < 5; i++ {
fmt.Println(scanner.Text())
}
if err := scanner.Err(); err != nil {
panic(err)
}
}
}
在for循环中使用defer是否正确?我收到了“可能资源泄漏”的警告。
jameswang2015:
func worker(url <-chan string){
这种做法比较奇怪。传递通道时应该使用 func worker(url chan string)。
jameswang2015:
resp, err := http.Get(<-url)
执行网络请求时应该设置内部超时。这很重要,因为你永远不知道会遇到什么错误,你的应用程序应该足够独立地取消调用。这里有个很好的例子:
client := http.Client{
Timeout: 5 * time.Second,
}
resp, err := client.Get(url)
...
jameswang2015:
var wg sync.WaitGroup
你的并发设计不清晰且缺乏规划。否则你不会使用全局变量。你可以尝试以下几种方式:
- 在不使用并发的情况下执行
http.Client.Get。先在main函数中完成所有操作。 - 仍然只在
main函数中处理,将http.Client.Get抽象到独立的函数中。 - 规划好你的并发设计(为什么我会遇到间歇性死锁错误?)。
记住,如果你无法在单个进程中处理,就不要考虑使用并发来倍增问题。
感谢!!我正在学习带通道的goroutine,注意到对于无缓冲通道,必须在主函数的goroutine之前使用(无论是向通道发送还是从通道接收)。(同意吗?)但我在编码时忘记了这个规则——感谢指出!我只是好奇为什么这段代码没有像往常一样报告"死锁"错误,而是直接卡住了——是因为我使用了http.get吗?
我也更新了代码如下,现在可以正常工作了——如果有其他实现方式请告诉我。谢谢!
package main
import (
"bufio"
"fmt"
"net/http"
"sync"
)
var wg sync.WaitGroup
func worker(url <-chan string){
defer wg.Done()
resp, err := http.Get(<-url)
if err != nil {
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 {
panic(err)
}
}
func main() {
wg.Add(3)
url := make(chan string)
urls := []string{
"http://gobyexample.com",
"http://att.com",
"http://domaintools.com",
"http://microsoft.com",
"http://google.com",
}
for i := 0; i < 3; i++ {
go worker(url)
}
for i := 0; i < 3; i++ {
url <- urls[i]
}
wg.Wait()
}
问题在于通道操作和goroutine启动的顺序错误,导致死锁。代码中先向无缓冲通道发送数据,然后启动goroutine,但此时没有接收者,造成阻塞。
具体问题:
- 主goroutine在向通道发送数据时,没有其他goroutine在接收
- 通道操作阻塞了goroutine的启动
修正后的代码:
package main
import (
"bufio"
"fmt"
"net/http"
"sync"
)
var wg sync.WaitGroup
func worker(url <-chan string) {
defer wg.Done()
for u := range url {
resp, err := http.Get(u)
if err != nil {
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 {
panic(err)
}
resp.Body.Close()
}
}
func main() {
wg.Add(3)
url := make(chan string)
// 先启动worker goroutine
for i := 0; i < 3; i++ {
go worker(url)
}
urls := []string{
"http://gobyexample.com",
"http://att.com",
"http://domaintools.com",
"http://microsoft.com",
"http://google.com",
}
// 然后发送数据
for i := 0; i < 3; i++ {
url <- urls[i]
}
close(url)
wg.Wait()
}
主要修改:
- 先启动goroutine再发送数据
- 使用
for range循环从通道读取 - 添加了
resp.Body.Close()释放资源 - 在处理完所有URL后关闭通道
这样确保有接收者准备好后再发送数据,避免死锁。


