转投go系列-channel 堵塞问题

channel是Go语言中的一个核心类型,可以把它看成管道。编程难度肯定是有一些的,哈哈。

“Go 强调不要通过共享内存来通讯,而是通过通讯来共享内存。”
很多人都说上面的话,具体怎么理解呢?

chan是一种引用类型,引用类型 channel可用于多个 goroutine 通讯。其内部实现了同步,确保并发安全。

举个例子:

package main
import "fmt"

func main() {
    
    
	ch := make(chan int) 
	go func() {
    
            
		for i := 0; i < 5; i++ {
    
    
			ch <- i 
			fmt.Println("input", i)
		}
	}()

	for i := 0; i < 5; i++ {
    
    
		num := <-ch
		fmt.Println("output", num)
	}
}

打印结果:

$ go run main.go
input 0
output 0
output 1
input 1
input 2
output 2
output 3
input 3
input 4
output 4

可以看到input output都是成对出现的,因为无缓冲chan,当只有读,没有写时,“读”阻塞。当只有写,没有读,那么 “写”阻塞。

  • 接下来是个反例:
package main
func main(){
    
    
    ch:=make(chan int)
    ch <- 1    
    go func (){
    
    
        <-ch  
    }()
}

这个就会卡死,因为创建goroutine在chan的写之后,永远不会执行读。

  • 另一个反例:
package main

import (
	"fmt"
	"time"
)

func main() {
    
    
	fmt.Println("log:", doReq(3*time.Second))
}

func doReq(timeout time.Duration) (res int) {
    
    
	ch := make(chan int)
	go func() {
    
    
		res := do()
		ch <- res
	}()
	select {
    
    
	case res = <-ch:
		return res
	case <-time.After(timeout):
		return 0
	}
}

func do() int {
    
    
	time.Sleep(4 * time.Second)
	return 111
}

我们创建了无缓冲ch,goroutine 执行 do 函数并通过ch 将结果发送回main,这是goroutine阻塞,直到main从 ch 接收到结果为止。同时main将在 select 阻塞,直到goroutine 将结果发送给 ch或超时3秒。如果超时先发生,则main将return 0,这会导致goroutine 写 ch 数据一直堵塞。这样内存就泄露啦!

  • 最后一个反例:
package main

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

var rw sync.RWMutex

func write(a int, out chan<- int) {
    
    
	for {
    
    
		a++
		rw.Lock()
		out <- a
		fmt.Printf("goroutine 写%d\n", a)
		rw.Unlock()
	}
}

func read(in <-chan int) {
    
    
	for {
    
    
		time.Sleep(time.Millisecond * 500)
		rw.RLock()
		a := <-in
		fmt.Printf("goroutine 读 %d\n", a)
		rw.RUnlock()
	}
}

func main() {
    
    
	ch := make(chan int, 5)
	for i := 0; i < 100; i++ {
    
    
		go read(ch)
	}
	for i := 0; i < 100; i++ {
    
    
		go write(0, ch)
	}
	for {
    
    
		runtime.GC()
	}
}

这种就是锁跟chan相互抢占cpu的反例,因为写的时候会不断的上写锁,读的时候拿不到读锁,也就没办法读chan,连带着写chan也会因为达到limit而阻塞,最后写锁也不会被释放。最后就死锁了。

猜你喜欢

转载自blog.csdn.net/lwcbest/article/details/121351694