Golang中所有goroutine都处于休眠状态 - 死锁问题!

Golang中所有goroutine都处于休眠状态 - 死锁问题! 我阅读了这个这个这个,但它们都没有解决我的问题。

我尝试异步读取两个文件,所以我写了下面的代码:

//readlines.go
package main

import (
	"bufio"
	"os"
)

// readLines 将整个文件读入内存
// 并返回其行的切片。
func readLines(path string) ([]string, error) {
	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	var lines []string
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
	}
	return lines, scanner.Err()
}

并像这样调用它:

package main

import (
	"fmt"
	"os"

	"github.com/gocarina/gocsv"
)

func (s *stocks) Read() {
	fmt.Println("Reading")
	stockFile, err := os.OpenFile("current_invenory.csv", os.O_RDWR|os.O_CREATE, os.ModePerm)
	if err != nil {
		panic(err)
	}
	defer stockFile.Close()
	stocks := []systemStock{}
	if err := gocsv.UnmarshalFile(stockFile, &stocks); err != nil { // 从文件加载库存
		panic(err)
	}

	*s = stocks
}

package main

import (
	"fmt"
	"os"

	"github.com/gocarina/gocsv"
)

func (t *transactions) Read() {
	fmt.Println("Reading")
	trxFile, err := os.OpenFile("current_transactions.csv", os.O_RDWR|os.O_CREATE, os.ModePerm)
	if err != nil {
		panic(err)
	}
	defer trxFile.Close()
	trx := []systemTransactions{}
	if err := gocsv.UnmarshalFile(trxFile, &trx); err != nil { // 从文件加载库存
		panic(err)
	}

	*t = trx
}

上面的代码与以下部分配合工作得很好:

	stock := stocks{} 
	trx := transactions{}

	stock.Read()
	trx.Read()
	for _, s := range stock {
			fmt.Println("Hello", s.Code)
	}

但是当我尝试像下面这样读取它们时,会报错 fatal error: all goroutines are asleep - deadlock!

	cs, ct := readData()

	for _, s := range cs {
		fmt.Println("Hello", s.Code)
	}

	for _, t := range ct {
		fmt.Println("Hello trx of ", t.Code)
	}

使用了

import "sync"

//func readData(cs chan stocks, ct chan transactions) (stocks, transactions) {
func readData() (stocks, transactions) {
	var wg sync.WaitGroup
	defer wg.Done()

	stock := stocks{}
	trx := transactions{}

	wg.Add(1)
	go stock.Read()
	wg.Add(1)
	go trx.Read()

	wg.Wait()

	return stock, trx
}

所以这个错误与我最后一块代码中做错(或不理解)的某些事情有关。


更多关于Golang中所有goroutine都处于休眠状态 - 死锁问题!的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

嗨,@hyousef,你在开始读取每个CSV文件时(正确地)向等待组添加了1,这使得等待组内部计数器变为2,但wg.Wait()会一直等待,直到该计数器降为零,而你没有任何调用wg.Done()来减少它。我建议将go stock.Read()改为:

go func() {
    defer wg.Done()
    stock.Read()
}()

然后对交易记录的处理也做同样的修改。

更多关于Golang中所有goroutine都处于休眠状态 - 死锁问题!的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你的代码存在几个关键问题导致了死锁。主要问题是:

  1. defer wg.Done() 位置错误:它在函数开始时就被调用,而不是在goroutine结束时
  2. 缺少通道同步:goroutine修改了共享变量,但没有适当的同步机制
  3. WaitGroup使用不当wg.Done()应该在每个goroutine结束时调用

以下是修正后的代码:

import "sync"

func readData() (stocks, transactions) {
    var wg sync.WaitGroup
    var mu sync.Mutex
    
    stock := stocks{}
    trx := transactions{}
    
    wg.Add(1)
    go func() {
        defer wg.Done()
        stock.Read()
    }()
    
    wg.Add(1)
    go func() {
        defer wg.Done()
        trx.Read()
    }()
    
    wg.Wait()
    return stock, trx
}

或者使用通道来避免数据竞争:

func readData() (stocks, transactions) {
    var wg sync.WaitGroup
    stockChan := make(chan stocks, 1)
    trxChan := make(chan transactions, 1)
    
    wg.Add(1)
    go func() {
        defer wg.Done()
        stock := stocks{}
        stock.Read()
        stockChan <- stock
    }()
    
    wg.Add(1)
    go func() {
        defer wg.Done()
        trx := transactions{}
        trx.Read()
        trxChan <- trx
    }()
    
    wg.Wait()
    close(stockChan)
    close(trxChan)
    
    return <-stockChan, <-trxChan
}

如果你需要保持原始的方法签名不变,但修复并发问题:

func readData() (stocks, transactions) {
    var wg sync.WaitGroup
    var mu sync.Mutex
    
    stock := stocks{}
    trx := transactions{}
    
    wg.Add(2)
    
    go func() {
        defer wg.Done()
        mu.Lock()
        stock.Read()
        mu.Unlock()
    }()
    
    go func() {
        defer wg.Done()
        mu.Lock()
        trx.Read()
        mu.Unlock()
    }()
    
    wg.Wait()
    return stock, trx
}

死锁发生的原因是:defer wg.Done()在函数开始时执行,立即减少了WaitGroup的计数器,然后wg.Wait()发现计数器已经为0,直接返回。但此时两个goroutine还没有开始执行,主goroutine继续执行到return语句,而两个后台goroutine试图修改共享变量,但没有同步机制,导致运行时检测到所有goroutine都处于休眠状态。

回到顶部