[英]How to unsubscribe from this Go channel before it is empty?
我不明白為什么這段代碼不起作用:
游樂場 REPL: https://play.golang.org/p/4PrKFnaTeKp
2009/11/10 23:00:01 published message to 0 subscribers
2009/11/10 23:00:01 published message to 0 subscribers
2009/11/10 23:00:02 client 1 connected
2009/11/10 23:00:02 published message to 1 subscribers
2009/11/10 23:00:02 message received: a message for 1
2009/11/10 23:00:02 receivedMsgs: 1
2009/11/10 23:00:02 message received: a message for all
2009/11/10 23:00:02 receivedMsgs: 2
2009/11/10 23:00:02 published message to 1 subscribers
2009/11/10 23:00:03 published message to 1 subscribers
2009/11/10 23:00:03 message received: a message for 1
2009/11/10 23:00:03 receivedMsgs: 3
2009/11/10 23:00:03 message received: a message for all
2009/11/10 23:00:03 receivedMsgs: 4
2009/11/10 23:00:03 published message to 1 subscribers
2009/11/10 23:00:04 published message to 1 subscribers
2009/11/10 23:00:04 message received: a message for 1
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [select (no cases)]:
main.main()
/tmp/sandbox948233627/prog.go:70 +0xfb
goroutine 6 [chan send]:
main.(*Broker).Publish(0xc000010240, 0x4c9815, 0x11, 0x0, 0x0)
/tmp/sandbox948233627/prog.go:121 +0x2af
main.main.func1(0xc000010240)
/tmp/sandbox948233627/prog.go:34 +0x91
created by main.main
/tmp/sandbox948233627/prog.go:30 +0xc7
goroutine 7 [semacquire]:
sync.runtime_SemacquireMutex(0xc000018054, 0x0, 0x1)
/usr/local/go-faketime/src/runtime/sema.go:71 +0x47
sync.(*Mutex).lockSlow(0xc000018050)
/usr/local/go-faketime/src/sync/mutex.go:138 +0x105
sync.(*Mutex).Lock(...)
/usr/local/go-faketime/src/sync/mutex.go:81
main.(*Broker).Unsubscribe(0xc000010240, 0xc000062060)
/tmp/sandbox948233627/prog.go:93 +0x1c5
main.main.func2(0xc000010240)
/tmp/sandbox948233627/prog.go:61 +0x1a5
created by main.main
/tmp/sandbox948233627/prog.go:40 +0xf6
package main
import (
"fmt"
"log"
"sync"
"time"
)
type Event struct {
Message string
Consumer string
}
func NewEvent(msg string, consumer string) Event {
return Event{
Message: msg,
Consumer: consumer,
}
}
type Broker struct {
consumers map[chan Event]string
mtx *sync.Mutex
}
func main() {
broker := NewBroker()
go func() {
for {
time.Sleep(time.Second * 1)
broker.Publish(NewEvent("a message for 1", "1"))
broker.Publish(NewEvent("a message for all", ""))
}
}()
time.Sleep(2 * time.Second)
go func() {
ch := broker.Subscribe("1")
receivedMsgs := 0
for {
msg := <-ch
//---> Here I'm sending message to client's browser
//if _, err := w.Write([]byte(fmt.Sprintf("data: %s\n\n", msg))); err != nil {
// log.Println(err)
// return
//}
//---> Here I unsubscribe if error in w.Flush() AKA browser was closed
//if err := w.Flush(); err != nil {
//log.Println("browser closed")
//broker.Unsubscribe(ch)
//return
//}
log.Println("message received:", msg.Message)
if receivedMsgs > 3 {
broker.Unsubscribe(ch)
break
}
receivedMsgs++
log.Println("receivedMsgs:", receivedMsgs)
}
}()
select {}
}
func NewBroker() *Broker {
return &Broker{
consumers: make(map[chan Event]string),
mtx: new(sync.Mutex),
}
}
func (b *Broker) Subscribe(id string) chan Event {
b.mtx.Lock()
defer b.mtx.Unlock()
c := make(chan Event)
b.consumers[c] = id
log.Println(fmt.Sprintf("client %s connected", id))
return c
}
func (b *Broker) Unsubscribe(c chan Event) {
b.mtx.Lock()
defer b.mtx.Unlock()
id := b.consumers[c]
close(c)
delete(b.consumers, c)
log.Printf("client %s killed, %d remaining\n", id, len(b.consumers))
}
func (b *Broker) Publish(e Event) {
b.mtx.Lock()
defer b.mtx.Unlock()
pubMsg := 0
for s, id := range b.consumers {
if e.Consumer != "" {
// Push to specific consumer
if id == e.Consumer {
s <- e
pubMsg++
break
}
} else {
// Push to every consumer
e.Consumer = id
s <- e
// Reset unused consumer
e.Consumer = ""
pubMsg++
}
}
log.Printf("published message to %d subscribers\n", pubMsg)
}
啟動時:
for
因此go func
但我收到一個錯誤:如果我改變這一行:
if receivedMsgs > 2 {
至
if receivedMsgs > 3 {
有用。
我認為問題在於,當我調用break
時,通道ch
中仍有一條消息。
我對嗎?
如何解決這個問題?
我受到https://gist.github.com/maestre3d/4a42e8fa552694f7c97c4811ce913e23 的啟發。
問題是空的 select。 根據這個網站在此處輸入鏈接描述,當使用空的 select 時,select 語句將永遠阻塞,因為沒有可用的 goroutine 可提供任何數據。
為了解決這個問題,我添加了一個帶有取消的上下文,這將允許我在取消訂閱時停止底部 select 並停止第一個 goroutine https://play.golang.org/p/tZklz7iiwGd ,我也會刪除不必要的互斥鎖正在使用通道,這應該是線程安全的。 而且我還讓消息一個接一個地執行,就好像我將兩者都發送到同一個 goroutine 上,這將導致並發,我可以將消息發送到關閉的通道。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.