Golang中解决死锁错误的常见方法

6 回复

不知道 strings.Fields(string) 这个函数。不错。

更多关于Golang中解决死锁错误的常见方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


谢谢。它在 Playground 上无法运行,因为那里禁止所有互联网请求。正如你所说,它在本地可以正常工作。

我在循环前添加了一个 fmt.Printf,发现 sites 中的第一个 v 是空的。这意味着你向等待组添加了 6 个计数,但只调用了 5 次 Done。我建议改为这样做:

func main() {
    fmt.Println("hello world")
}

@skillian @Unique 另外,我认为像这里 https://play.golang.org/p/uoPsczEUXon 这样使用 sites := strings.Fields(b.String()) 会更好。 它在 Playground 中会抛出一个讨厌的错误,但在本地 IDE 中一切正常。

func main() {
    fmt.Println("hello world")
}

您也可以在迭代之前从切片中过滤掉空字符串,像这样 https://play.golang.org/p/RcTQomu2j7d (1)

仍然建议像skillian所做的那样添加到WaitGroup中。

(1) 由于未知原因,我的示例在playgroup上无法运行,但在本地使用go版本go1.14 windows/amd64可以正常工作。

在Golang中解决死锁错误,关键在于确保所有goroutine都能正确完成且channel操作保持平衡。根据你提供的代码,主要问题在于channel使用不当导致goroutine阻塞。

问题分析

你的代码存在以下问题:

  1. 无缓冲channel在发送操作后会阻塞,直到有接收操作
  2. wg.Wait()在goroutine完成前被调用,但goroutine被channel阻塞无法完成
  3. 缺少独立的goroutine来接收channel数据

修正方案

方案1:使用带缓冲的channel

package main

import (
    "fmt"
    "net/http"
    "sync"
)

func fetchStatus(wg *sync.WaitGroup, url string, ch chan string) {
    defer wg.Done()
    resp, err := http.Get(url)
    if err != nil {
        ch <- fmt.Sprintf("%s: %v", url, err)
        return
    }
    ch <- fmt.Sprintf("%s: %d", url, resp.StatusCode)
}

func main() {
    urls := []string{
        "https://golang.org",
        "https://google.com",
        "https://github.com",
    }
    
    ch := make(chan string, len(urls)) // 使用带缓冲的channel
    var wg sync.WaitGroup
    
    for _, url := range urls {
        wg.Add(1)
        go fetchStatus(&wg, url, ch)
    }
    
    wg.Wait()
    close(ch)
    
    for result := range ch {
        fmt.Println(result)
    }
}

方案2:使用独立的接收goroutine

package main

import (
    "fmt"
    "net/http"
    "sync"
)

func fetchStatus(wg *sync.WaitGroup, url string, ch chan string) {
    defer wg.Done()
    resp, err := http.Get(url)
    if err != nil {
        ch <- fmt.Sprintf("%s: %v", url, err)
        return
    }
    ch <- fmt.Sprintf("%s: %d", url, resp.StatusCode)
}

func main() {
    urls := []string{
        "https://golang.org",
        "https://google.com",
        "https://github.com",
    }
    
    ch := make(chan string)
    var wg sync.WaitGroup
    
    // 启动接收goroutine
    go func() {
        wg.Wait()
        close(ch)
    }()
    
    for _, url := range urls {
        wg.Add(1)
        go fetchStatus(&wg, url, ch)
    }
    
    // 在主goroutine中接收结果
    for result := range ch {
        fmt.Println(result)
    }
}

方案3:使用select避免阻塞(推荐)

package main

import (
    "fmt"
    "net/http"
    "sync"
)

func fetchStatus(wg *sync.WaitGroup, url string, ch chan string) {
    defer wg.Done()
    resp, err := http.Get(url)
    if err != nil {
        ch <- fmt.Sprintf("%s: %v", url, err)
        return
    }
    ch <- fmt.Sprintf("%s: %d", url, resp.StatusCode)
}

func main() {
    urls := []string{
        "https://golang.org",
        "https://google.com",
        "https://github.com",
    }
    
    ch := make(chan string)
    var wg sync.WaitGroup
    done := make(chan bool)
    
    // 启动接收goroutine
    go func() {
        for result := range ch {
            fmt.Println(result)
        }
        done <- true
    }()
    
    for _, url := range urls {
        wg.Add(1)
        go fetchStatus(&wg, url, ch)
    }
    
    // 等待所有goroutine完成
    go func() {
        wg.Wait()
        close(ch)
    }()
    
    <-done // 等待接收完成
}

关键要点

  1. channel容量:无缓冲channel需要发送和接收同时就绪,否则会阻塞
  2. goroutine生命周期:确保所有goroutine都能正常退出
  3. WaitGroup使用wg.Wait()应在所有wg.Add()调用之后
  4. channel关闭:发送完成后及时关闭channel,避免接收方永久阻塞

这些修正方案都能有效解决死锁问题,方案3提供了更好的并发控制和资源管理。

回到顶部