在Go語言中,程序的并發操作是通過通道(channel)來實現的。通道是用來傳遞數據的一種特殊類型,它可以在goroutine之間進行數據交換和通信。然而,如果在程序中使用單通道進行工作,并在引入新通道時沒有正確處理,就有可能導致死鎖現象的發生。本文將由php小編小新為大家詳細解釋Go程序中單通道工作和死鎖問題,以及如何避免死鎖的發生。
問題內容
我是Go通道的新手,我正在嘗試通過構建模擬內核并通過通道處理交互來學習Go通道。此示例程序的目標是讓多個進程 (2) 使用單通道同時向內核發送內存分配請求,其他進程發送釋放內存請求 使用單個但不同的通道到內核。
+-------------+
+------------------+ | |
-> Alloc. Mem. Ch. |-->| Kernel |
| Process A | | Realse Mem. Ch. |< | |
+------------------+ +-------------+
登錄后復制
如果我只有分配請求,程序就可以工作,一旦我引入釋放請求,程序就會陷入死鎖。
請注意,進程在發送分配請求時也會創建一個回復隊列,但是,這在上圖中沒有顯示,因為它不是問題的一部分。
完整的程序如下:
package main
import (
"fmt"
// "log"
"time"
)
const (
_ float64 = iota
LowPrio
MedPrio
HghPrio
)
// Kernel type to communicate between processes and memory resources
type Kernel struct {
reqMemCh chan chan int
rlsMemCh chan int
}
func (k *Kernel) Init() {
k.reqMemCh = make(chan chan int, 2)
k.rlsMemCh = make(chan int, 2)
go k.AllocMem()
go k.RlsMem()
}
// Fetch memory on process request
func (k *Kernel) GetReqMemCh() chan chan int {
return k.reqMemCh
}
func (k *Kernel) GetRlsMemCh() chan int {
return k.rlsMemCh
}
func (k *Kernel) AllocMem() {
// loop over the items (process reply channels) received over
// the request channel
for pCh := range k.GetReqMemCh() {
// for now think 0 is the available index
// send this as a reply to the exclusive process reply channel
pCh <- 0
close(pCh)
}
}
// Release memory
func (k *Kernel) RlsMem() {
// we do not have to anything here
}
// Process type which requests memory
type Proc struct {
ind int
prio float64
exeT time.Time
count int
memInd int
rqMemCh chan chan int
rlMemCh chan int
}
func (p *Proc) Init(
ind int,
prio float64,
rqMemCh chan chan int,
rlMemCh chan int,
) {
p.ind = ind
p.prio = prio
p.memInd = -1
p.rqMemCh = rqMemCh
p.rlMemCh = rlMemCh
}
func (p *Proc) GetReqMemCh() chan chan int {
return p.rqMemCh
}
func (p *Proc) GetRlsMemCh() chan int {
return p.rlMemCh
}
func (p *Proc) ReqMem() {
// create the reply channel exclusive to the process
// this channel will return the allocated memeory id/address
rpCh := make(chan int)
// send the reply channel through the request channel
// to get back the allocation memory id
p.GetReqMemCh() <- rpCh
// Below line is blocking ...
for mi := range rpCh {
p.memInd = mi
}
}
func (p Proc) RlsMem() {
p.GetRlsMemCh() <- 0
}
func (p Proc) String() string {
return fmt.Sprintf(
"Proc(%d): Memory(%d), Count(%d)",
p.ind+1, p.memInd+1, p.count,
)
}
func main() {
k := &Kernel{}
k.Init()
p := &Proc{}
for i := 0; i < 3; i++ {
p.Init(i, LowPrio, k.GetReqMemCh(), k.GetRlsMemCh())
p.ReqMem()
p.RlsMem()
}
time.Sleep(time.Second)
}
登錄后復制
異常情況如下:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.Proc.RlsMem(...)
main.go:100
main.main()
main.go:119 +0xc5
goroutine 6 [chan receive]:
main.(*Kernel).AllocMem(0x0?)
main.go:41 +0x5e
created by main.(*Kernel).Init in goroutine 1
main.go:25 +0xc5
exit status 2
登錄后復制
任何幫助將不勝感激。
干杯,
DD。
解決方法
作為英國人評論,您有一個緩沖通道已達到其容量,但沒有任何內容可供讀取。
根據語言之旅 (1 2),發送和接收塊,直到另一方準備好。雖然緩沖通道在這里提供了一些寬容,但一旦緩沖區已滿,行為是相同的。
可以通過添加 k.rlsMemCh 的使用者來解決此問題。如果您沒有為此計劃任何操作,請刪除該通道或暫時使用邏輯將其耗盡。
func (k *Kernel) Init() {
k.reqMemCh = make(chan chan int, 2)
k.rlsMemCh = make(chan int, 2)
go k.AllocMem()
go k.RlsMem()
}
func (k *Kernel) AllocMem() {
for pCh := range k.GetReqMemCh() {
pCh <- 0
close(pCh)
}
}
func (k *Kernel) RlsMem() {
// TODO: Add a for-select or for-range over k.rlsMemCh here
}
登錄后復制
排水可能如下所示:
func (k *Kernel) RlsMem() {
for {
<-k.GetRlsMemCh()
}
}
登錄后復制






