Golang中如何解决死锁问题

Golang中如何解决死锁问题 不确定为何会出现死锁,尽管通道已关闭,并且我循环了所需的次数。

import (
	"fmt"
	"time"
)

func sth() {

	ticker := time.NewTicker(500 * time.Millisecond)

	done := make(chan bool)
	c := make(chan int)

	go func() {
		for i := 0; i < 10; i++ {
			select {
			case <-done:
				return
			case <-ticker.C:
				c <- i
			}
		}
		close(c)
	}()

	for i := 0; i < 10; i++ {
		fmt.Println(<-c)
	}
	

	time.Sleep(1 * time.Second)
	ticker.Stop()
	done <- true
	fmt.Println("Ticker stopped")
}

func main() {
	sth()
}

这是完整的代码。 这是链接 - https://play.golang.org/p/tQGt2rOQX3U


更多关于Golang中如何解决死锁问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中如何解决死锁问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这是一个典型的死锁问题,发生在主goroutine等待从通道c接收数据时。让我们分析一下:

package main

import (
	"fmt"
	"time"
)

func sth() {
	ticker := time.NewTicker(500 * time.Millisecond)
	done := make(chan bool)
	c := make(chan int)

	go func() {
		for i := 0; i < 10; i++ {
			select {
			case <-done:
				return
			case <-ticker.C:
				c <- i  // 这里可能阻塞
			}
		}
		close(c)
	}()

	// 问题在这里:主goroutine等待从c接收10次
	for i := 0; i < 10; i++ {
		fmt.Println(<-c)  // 这里会阻塞等待
	}

	time.Sleep(1 * time.Second)
	ticker.Stop()
	done <- true  // 这行永远不会执行到,因为上面的循环已经阻塞了
	fmt.Println("Ticker stopped")
}

func main() {
	sth()
}

死锁原因分析:

  1. 主goroutine启动了一个工作goroutine
  2. 工作goroutine使用select尝试向通道c发送数据
  3. 主goroutine在for循环中等待从c接收10次数据
  4. 但工作goroutine的ticker每500毫秒才触发一次,发送速度慢于主goroutine的接收速度
  5. 当主goroutine等待第10个数据时,工作goroutine可能还没有发送第10个数据
  6. 两个goroutine相互等待,形成死锁

解决方案1:使用缓冲通道

func sth() {
	ticker := time.NewTicker(500 * time.Millisecond)
	done := make(chan bool)
	c := make(chan int, 10)  // 添加缓冲区

	go func() {
		for i := 0; i < 10; i++ {
			select {
			case <-done:
				return
			case <-ticker.C:
				c <- i
			}
		}
		close(c)
	}()

	for i := 0; i < 10; i++ {
		fmt.Println(<-c)
	}

	ticker.Stop()
	done <- true
	fmt.Println("Ticker stopped")
}

解决方案2:使用range循环接收

func sth() {
	ticker := time.NewTicker(500 * time.Millisecond)
	done := make(chan bool)
	c := make(chan int)

	go func() {
		for i := 0; i < 10; i++ {
			select {
			case <-done:
				return
			case <-ticker.C:
				c <- i
			}
		}
		close(c)
	}()

	// 使用range自动检测通道关闭
	for v := range c {
		fmt.Println(v)
	}

	ticker.Stop()
	done <- true
	fmt.Println("Ticker stopped")
}

解决方案3:确保发送goroutine先完成

func sth() {
	ticker := time.NewTicker(500 * time.Millisecond)
	done := make(chan bool)
	c := make(chan int)

	go func() {
		defer close(c)
		for i := 0; i < 10; i++ {
			select {
			case <-done:
				return
			case <-ticker.C:
				c <- i
			}
		}
	}()

	// 等待工作goroutine完成
	for i := 0; i < 10; i++ {
		fmt.Println(<-c)
	}

	ticker.Stop()
	done <- true
	fmt.Println("Ticker stopped")
}

解决方案4:使用WaitGroup同步

import (
	"fmt"
	"sync"
	"time"
)

func sth() {
	ticker := time.NewTicker(500 * time.Millisecond)
	done := make(chan bool)
	c := make(chan int)
	var wg sync.WaitGroup

	wg.Add(1)
	go func() {
		defer wg.Done()
		defer close(c)
		for i := 0; i < 10; i++ {
			select {
			case <-done:
				return
			case <-ticker.C:
				c <- i
			}
		}
	}()

	for i := 0; i < 10; i++ {
		fmt.Println(<-c)
	}

	wg.Wait()
	ticker.Stop()
	done <- true
	fmt.Println("Ticker stopped")
}

最推荐使用解决方案2,因为它最简洁且能正确处理通道关闭的情况。使用range循环可以确保在通道关闭后自动退出循环,避免死锁。

回到顶部