all goroutines are asleep - deadlock-Go中协程死锁详解

最近学go的channel的时候老是遇到题目中的错误:
fatal error: all goroutines are asleep - deadlock!
对问题研究总结后,在此记录一下

下贴上错误代码:

func testDeadLock(c chan int){
	for{
		fmt.Println(<-c)
	}
}

func main() {
	c :=make(chan int)
	c<-'A'
	go testDeadLock(c)
	time.Sleep(time.Millisecond)
}

看上面main函数中,先开了一个通道channel c,然后马上对这个c内写入数据。
然后调协程testDeadLock在c中取数据,结果运行时报错:
fatal error: all goroutines are asleep - deadlock!

这究竟是咋回事呢?
首先我们这里通过make(chan int),开辟的通道是一种无缓冲通道,所以当对这个缓冲通道写的时候,会一直阻塞等到某个协程对这个缓冲通道读(大家发现没有这个与典型的生产者消费者有点不一样,当队列中“内容”已经满了,生产者再生往里放东西才会阻塞,而这里我讲c<-'A’理解为生产,他却是需要等到某个协程读了再能继续运行)。
main函数的执行在go语言中本身就是一个协程的执行,所以在执行到c<-'A’的时候,执行main函数的协程将被阻塞,换句话说main函数被阻塞了,此时不能在继续往下执行了,所以go testDeadLock©这个函数无法执行到了,就无法读到c中的内容了,所以整个程序阻塞,发生了死锁。

如何解决这个问题呢?大家想一下,我们阻塞的原因是main函数不能继续向下执行了,所以go testDeadLock©执行不到,对吧,那我们在main函数中另开一个协程,让他对c进行写(对c进行写的过程不在main函数对应的协程中做),修改后的代码:

func test(c chan int){
	c<-'A'
}

func testDeadLock(c chan int){
	for{
		fmt.Println(<-c)
	}
}

func main() {
	//chanDemo()
	c :=make(chan int)
	go test(c)
	go testDeadLock(c)
	time.Sleep(time.Millisecond)

}

新增了test函数,在main函数中为test函数开了协程专门执行,让对c的写在test中进行,这样main函数就能继续往下执行,去开一个协程执行testDeadLock函数了,这样一来,虽然刚刚开的test函数中会因为对c的写入发生阻塞但并不会影响main函数对应协程的继续执行,所以之后的testDeadLock马上将从c中读出test函数刚刚写入的内容,这样一来 test中对c写入的内容就被testDeadLock顺利读出并打印了。

  • 21
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 20
    评论
好的,以下是三道题的详细代码实现: 1. 自定义sendData()函数并发发送数据 ```go package main import ( "fmt" ) func sendData(ch chan string) { ch <- "beijing" ch <- "shanghai" ch <- "hangzhou" close(ch) } func main() { ch := make(chan string) go sendData(ch) for data := range ch { fmt.Println(data) } } ``` 在该代码,我们定义了sendData()函数,该函数会并发向通道发送三个字符串,并关闭通道。在主函数,我们创建了一个通道,然后开启一个协程执行sendData()函数。主函数通过range循环从通道获取并打印数据,由于通道在sendData()函数被关闭,所以range循环可以正常结束。 2. 并发售票功能 ```go package main import ( "fmt" "sync" ) var ( tickets = 10 // 总票数 wg sync.WaitGroup mutex sync.Mutex // 互斥锁 ) func sellTicket(ch chan int, id int) { defer wg.Done() for { mutex.Lock() if tickets > 0 { tickets-- fmt.Printf("售票员%d售出一张票,剩余%d张票\n", id, tickets) mutex.Unlock() ch <- 1 // 向通道发送数据 } else { mutex.Unlock() fmt.Printf("售票员%d售票完成\n", id) break // 售票完成,跳出循环 } } } func main() { ch := make(chan int) for i := 1; i <= 3; i++ { wg.Add(1) go sellTicket(ch, i) } // 等待所有售票员完成售票 go func() { wg.Wait() close(ch) }() // 从通道接收数据,直到通道关闭 for range ch { } } ``` 在该代码,我们定义了sellTicket()函数,该函数在一个for循环不断地售出票。我们使用互斥锁来保证同时只有一个售票员能够售出票,然后把售票的结果通过通道发送出去。在主函数,我们创建了一个长度为0的通道,并开启了三个协程代表三个售票员来售票。最后,我们通过一个匿名协程来等待售票员售票完成并关闭通道,然后通过range循环从通道获取并忽略数据,直到通道关闭。 3. Web程序并发售票功能 ```go package main import ( "fmt" "net/http" "sync" ) var ( tickets = 10 // 总票数 mutex sync.Mutex // 互斥锁 ) func sellTicket(w http.ResponseWriter, r *http.Request) { mutex.Lock() defer mutex.Unlock() if tickets > 0 { tickets-- fmt.Fprintf(w, "售出一张票,剩余%d张票\n", tickets) } else { fmt.Fprint(w, "票已售完\n") } } func main() { http.HandleFunc("/sell", sellTicket) err := http.ListenAndServe(":8080", nil) if err != nil { fmt.Println("ListenAndServe: ", err) } } ``` 在该代码,我们定义了一个sellTicket()函数,该函数会在请求时售出一张票,如果票已售完则返回"票已售完"的信息。我们使用互斥锁来保证同时只有一个请求能够访问售票逻辑。在主函数,我们使用http包来监听8080端口,并将sellTicket()函数注册到"/sell"的路由上。当有请求到达"/sell"时,sellTicket()函数会被调用并售出一张票或返回"票已售完"的信息。
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值