如果 select 具有默认值,则不会发送到频道
Sending to channel doesn't happen if select has default
我正在开展一个个人项目,该项目将 运行 在 Raspberry Pi 上连接一些传感器。
从传感器读取的函数和处理套接字连接的函数在不同的 goroutine 中执行,因此,为了在从传感器读取数据时在套接字上发送数据,我创建了一个 chan []byte
在 main 函数中并将其传递给 goroutines.
我的问题出在这里:如果我连续多次写入,只有第一个数据到达客户端,而其他数据则不会。但是如果我在 sender 函数中放一点 time.Sleep
,所有的数据都会正确地到达客户端。
无论如何,这是这个小程序的简化版本:
package main
import (
"net"
"os"
"sync"
"time"
)
const socketName string = "./test_socket"
// create to the socket and launch the accept client routine
func launchServerUDS(ch chan []byte) {
if err := os.RemoveAll(socketName); err != nil {
return
}
l, err := net.Listen("unix", socketName)
if err != nil {
return
}
go acceptConnectionRoutine(l, ch)
}
// accept incoming connection on the socket and
// 1) launch the routine to handle commands from the client
// 2) launch the routine to send data when the server reads from the sensors
func acceptConnectionRoutine(l net.Listener, ch chan []byte) {
defer l.Close()
for {
conn, err := l.Accept()
if err != nil {
return
}
go commandsHandlerRoutine(conn, ch)
go autoSendRoutine(conn, ch)
}
}
// routine that sends data to the client
func autoSendRoutine(c net.Conn, ch chan []byte) {
for {
data := <-ch
if string(data) == "exit" {
return
}
c.Write(data)
}
}
// handle client connection and calls functions to execute commands
func commandsHandlerRoutine(c net.Conn, ch chan []byte) {
for {
buf := make([]byte, 1024)
n, err := c.Read(buf)
if err != nil {
ch <- []byte("exit")
break
}
// now, for sake of simplicity , only echo commands back to the client
_, err = c.Write(buf[:n])
if err != nil {
ch <- []byte("exit")
break
}
}
}
// write on the channel to the autosend routine so the data are written on the socket
func sendDataToClient(data []byte, ch chan []byte) {
select {
case ch <- data:
// if i put a little sleep here, no problems
// i i remove the sleep, only data1 is sent to the client
// time.Sleep(1 * time.Millisecond)
default:
}
}
func dummyReadDataRoutine(ch chan []byte) {
for {
// read data from the sensors every 5 seconds
time.Sleep(5 * time.Second)
// read first data and send it
sendDataToClient([]byte("dummy data1\n"), ch)
// read second data and send it
sendDataToClient([]byte("dummy data2\n"), ch)
// read third data and send it
sendDataToClient([]byte("dummy data3\n"), ch)
}
}
func main() {
ch := make(chan []byte)
wg := sync.WaitGroup{}
wg.Add(2)
go dummyReadDataRoutine(ch)
go launchServerUDS(ch)
wg.Wait()
}
我认为使用睡眠来同步写入是不正确的。如何解决此问题,同时将函数 运行ning 保留在不同的不同 goroutine 上。
JimB 解释的很好,我觉得他的回答比较好
我在这个答案中包含了我的部分解决方案。
我原以为我的代码是清晰和简化的,但正如 Jim 所说,我可以做得更简单和清晰。我保留了我的旧代码 posted,这样人们可以更好地理解如何 post 更简单的代码,而不是像我那样搞得一团糟。
正如chmike所说,我的问题并不像我想的那样与套接字有关,而只与频道有关。在无缓冲通道上写入是问题之一。将无缓冲通道更改为缓冲通道后,问题得到解决。无论如何,这段代码不是"good code",可以按照JimB在他的回答中写的原则进行改进。
所以这是新代码:
package main
import (
"net"
"os"
"sync"
"time"
)
const socketName string = "./test_socket"
// create the socket and accept clients connections
func launchServerUDS(ch chan []byte, wg *sync.WaitGroup) {
defer wg.Done()
if err := os.RemoveAll(socketName); err != nil {
return
}
l, err := net.Listen("unix", socketName)
if err != nil {
return
}
defer l.Close()
for {
conn, err := l.Accept()
if err != nil {
return
}
// this goroutine are launched when a client is connected
// routine that listen and echo commands
go commandsHandlerRoutine(conn, ch)
// routine to send data read from the sensors to the client
go autoSendRoutine(conn, ch)
}
}
// routine that sends data to the client
func autoSendRoutine(c net.Conn, ch chan []byte) {
for {
data := <-ch
if string(data) == "exit" {
return
}
c.Write(data)
}
}
// handle commands received from the client
func commandsHandlerRoutine(c net.Conn, ch chan []byte) {
for {
buf := make([]byte, 1024)
n, err := c.Read(buf)
if err != nil {
// if i can't read send an exit command to autoSendRoutine and exit
ch <- []byte("exit")
break
}
// now, for sake of simplicity , only echo commands back to the client
_, err = c.Write(buf[:n])
if err != nil {
// if i can't write back send an exit command to autoSendRoutine and exit
ch <- []byte("exit")
break
}
}
}
// this goroutine reads from the sensors and write to the channel , so data are sent
// to the client if a client is connected
func dummyReadDataRoutine(ch chan []byte, wg *sync.WaitGroup) {
x := 0
for x < 100 {
// read data from the sensors every 5 seconds
time.Sleep(1 * time.Second)
// read first data and send it
ch <- []byte("data1\n")
// read second data and send it
ch <- []byte("data2\n")
// read third data and send it
ch <- []byte("data3\n")
x++
}
wg.Done()
}
func main() {
// create a BUFFERED CHANNEL
ch := make(chan []byte, 1)
wg := sync.WaitGroup{}
wg.Add(2)
// launch the goruotines that handle the socket connections
// and read data from the sensors
go dummyReadDataRoutine(ch, &wg)
go launchServerUDS(ch, &wg)
wg.Wait()
}
主要问题出在函数中:
func sendDataToClient(data []byte, ch chan []byte) {
select {
case ch <- data:
// if I put a little sleep here, no problems
// if I remove the sleep, only data1 is sent to the client
// time.Sleep(1 * time.Millisecond)
default:
}
如果调用函数时通道 ch
未准备好,将采用 default
情况并且永远不会发送 data
。在这种情况下,您应该取消该功能并直接发送到频道。
缓冲通道与手头的问题是正交的,应该出于与缓冲 IO 类似的原因来完成,即为无法立即进行的写入提供 "buffer"。如果代码在没有缓冲区的情况下无法进行,添加缓冲区只会延迟可能出现的死锁。
您在这里也不需要 exit
哨兵值,因为您可以在频道上进行测距并在完成后将其关闭。然而,这仍然忽略了写入错误,但同样需要重新设计。
for data := range ch {
c.Write(data)
}
您还应该小心地通过通道传递切片,因为很容易忘记哪个逻辑进程拥有所有权并将修改支持数组。根据给出的信息,我不能说通过通道传递读+写数据是否改进了体系结构,但这不是您在大多数 Go 网络代码中会发现的模式。
我正在开展一个个人项目,该项目将 运行 在 Raspberry Pi 上连接一些传感器。
从传感器读取的函数和处理套接字连接的函数在不同的 goroutine 中执行,因此,为了在从传感器读取数据时在套接字上发送数据,我创建了一个 chan []byte
在 main 函数中并将其传递给 goroutines.
我的问题出在这里:如果我连续多次写入,只有第一个数据到达客户端,而其他数据则不会。但是如果我在 sender 函数中放一点 time.Sleep
,所有的数据都会正确地到达客户端。
无论如何,这是这个小程序的简化版本:
package main
import (
"net"
"os"
"sync"
"time"
)
const socketName string = "./test_socket"
// create to the socket and launch the accept client routine
func launchServerUDS(ch chan []byte) {
if err := os.RemoveAll(socketName); err != nil {
return
}
l, err := net.Listen("unix", socketName)
if err != nil {
return
}
go acceptConnectionRoutine(l, ch)
}
// accept incoming connection on the socket and
// 1) launch the routine to handle commands from the client
// 2) launch the routine to send data when the server reads from the sensors
func acceptConnectionRoutine(l net.Listener, ch chan []byte) {
defer l.Close()
for {
conn, err := l.Accept()
if err != nil {
return
}
go commandsHandlerRoutine(conn, ch)
go autoSendRoutine(conn, ch)
}
}
// routine that sends data to the client
func autoSendRoutine(c net.Conn, ch chan []byte) {
for {
data := <-ch
if string(data) == "exit" {
return
}
c.Write(data)
}
}
// handle client connection and calls functions to execute commands
func commandsHandlerRoutine(c net.Conn, ch chan []byte) {
for {
buf := make([]byte, 1024)
n, err := c.Read(buf)
if err != nil {
ch <- []byte("exit")
break
}
// now, for sake of simplicity , only echo commands back to the client
_, err = c.Write(buf[:n])
if err != nil {
ch <- []byte("exit")
break
}
}
}
// write on the channel to the autosend routine so the data are written on the socket
func sendDataToClient(data []byte, ch chan []byte) {
select {
case ch <- data:
// if i put a little sleep here, no problems
// i i remove the sleep, only data1 is sent to the client
// time.Sleep(1 * time.Millisecond)
default:
}
}
func dummyReadDataRoutine(ch chan []byte) {
for {
// read data from the sensors every 5 seconds
time.Sleep(5 * time.Second)
// read first data and send it
sendDataToClient([]byte("dummy data1\n"), ch)
// read second data and send it
sendDataToClient([]byte("dummy data2\n"), ch)
// read third data and send it
sendDataToClient([]byte("dummy data3\n"), ch)
}
}
func main() {
ch := make(chan []byte)
wg := sync.WaitGroup{}
wg.Add(2)
go dummyReadDataRoutine(ch)
go launchServerUDS(ch)
wg.Wait()
}
我认为使用睡眠来同步写入是不正确的。如何解决此问题,同时将函数 运行ning 保留在不同的不同 goroutine 上。
JimB 解释的很好,我觉得他的回答比较好
我在这个答案中包含了我的部分解决方案。
我原以为我的代码是清晰和简化的,但正如 Jim 所说,我可以做得更简单和清晰。我保留了我的旧代码 posted,这样人们可以更好地理解如何 post 更简单的代码,而不是像我那样搞得一团糟。
正如chmike所说,我的问题并不像我想的那样与套接字有关,而只与频道有关。在无缓冲通道上写入是问题之一。将无缓冲通道更改为缓冲通道后,问题得到解决。无论如何,这段代码不是"good code",可以按照JimB在他的回答中写的原则进行改进。
所以这是新代码:
package main
import (
"net"
"os"
"sync"
"time"
)
const socketName string = "./test_socket"
// create the socket and accept clients connections
func launchServerUDS(ch chan []byte, wg *sync.WaitGroup) {
defer wg.Done()
if err := os.RemoveAll(socketName); err != nil {
return
}
l, err := net.Listen("unix", socketName)
if err != nil {
return
}
defer l.Close()
for {
conn, err := l.Accept()
if err != nil {
return
}
// this goroutine are launched when a client is connected
// routine that listen and echo commands
go commandsHandlerRoutine(conn, ch)
// routine to send data read from the sensors to the client
go autoSendRoutine(conn, ch)
}
}
// routine that sends data to the client
func autoSendRoutine(c net.Conn, ch chan []byte) {
for {
data := <-ch
if string(data) == "exit" {
return
}
c.Write(data)
}
}
// handle commands received from the client
func commandsHandlerRoutine(c net.Conn, ch chan []byte) {
for {
buf := make([]byte, 1024)
n, err := c.Read(buf)
if err != nil {
// if i can't read send an exit command to autoSendRoutine and exit
ch <- []byte("exit")
break
}
// now, for sake of simplicity , only echo commands back to the client
_, err = c.Write(buf[:n])
if err != nil {
// if i can't write back send an exit command to autoSendRoutine and exit
ch <- []byte("exit")
break
}
}
}
// this goroutine reads from the sensors and write to the channel , so data are sent
// to the client if a client is connected
func dummyReadDataRoutine(ch chan []byte, wg *sync.WaitGroup) {
x := 0
for x < 100 {
// read data from the sensors every 5 seconds
time.Sleep(1 * time.Second)
// read first data and send it
ch <- []byte("data1\n")
// read second data and send it
ch <- []byte("data2\n")
// read third data and send it
ch <- []byte("data3\n")
x++
}
wg.Done()
}
func main() {
// create a BUFFERED CHANNEL
ch := make(chan []byte, 1)
wg := sync.WaitGroup{}
wg.Add(2)
// launch the goruotines that handle the socket connections
// and read data from the sensors
go dummyReadDataRoutine(ch, &wg)
go launchServerUDS(ch, &wg)
wg.Wait()
}
主要问题出在函数中:
func sendDataToClient(data []byte, ch chan []byte) {
select {
case ch <- data:
// if I put a little sleep here, no problems
// if I remove the sleep, only data1 is sent to the client
// time.Sleep(1 * time.Millisecond)
default:
}
如果调用函数时通道 ch
未准备好,将采用 default
情况并且永远不会发送 data
。在这种情况下,您应该取消该功能并直接发送到频道。
缓冲通道与手头的问题是正交的,应该出于与缓冲 IO 类似的原因来完成,即为无法立即进行的写入提供 "buffer"。如果代码在没有缓冲区的情况下无法进行,添加缓冲区只会延迟可能出现的死锁。
您在这里也不需要 exit
哨兵值,因为您可以在频道上进行测距并在完成后将其关闭。然而,这仍然忽略了写入错误,但同样需要重新设计。
for data := range ch {
c.Write(data)
}
您还应该小心地通过通道传递切片,因为很容易忘记哪个逻辑进程拥有所有权并将修改支持数组。根据给出的信息,我不能说通过通道传递读+写数据是否改进了体系结构,但这不是您在大多数 Go 网络代码中会发现的模式。