英文:
Why signal handler not work when I added another blocking thread in go?
问题
我正在尝试构建一个非常简单的TCP服务器/客户端,并且希望程序在通过Ctrl-C中断时能够关闭连接。
如果在主线程中只发送消息或只接收消息,一切都正常工作。
这是客户端的代码:
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"net"
"bufio"
"io"
"time"
)
const (
TIMEOUT = 10
)
func main() {
if len(os.Args) < 2 {
fmt.Println(usage(os.Args[0]))
return
}
var timeout time.Duration
if len(os.Args) > 2 {
timeout, _ = time.ParseDuration(os.Args[2])
}
if timeout == 0 {
timeout = time.Duration(TIMEOUT * time.Second)
}
conn, err := net.DialTimeout("tcp", os.Args[1], timeout)
if err != nil {
fmt.Println("Error connecting: ", err.Error())
return
}
defer conn.Close()
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
go func() {
fmt.Println("wait ctrl-c")
for _ = range c {
fmt.Println("close on ctrl-c")
conn.Close()
}
}()
for {
message, err := bufio.NewReader(os.Stdin).ReadString('\n')
if err == io.EOF {
fmt.Fprint(conn, message)
break
} else if err != nil {
fmt.Println("Error reading: ", err.Error())
break
} else {
fmt.Fprintf(conn, message)
}
}
}
func usage(filename string) string {
return fmt.Sprintf("Usage: %s <address (ex. localhost:8016, google.com:http, [2001:db8::1]:http, etc.)> [timeout (ex. 10s, 300ms, etc.)]", filename)
}
但是,当我添加了打印接收到的消息并在另一个线程中运行时,Ctrl-C处理程序不起作用。
这是代码:
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"net"
"bufio"
"io"
"time"
)
const (
TIMEOUT = 10
)
func main() {
if len(os.Args) < 2 {
fmt.Println(usage(os.Args[0]))
return
}
var timeout time.Duration
if len(os.Args) > 2 {
timeout, _ = time.ParseDuration(os.Args[2])
}
if timeout == 0 {
timeout = time.Duration(TIMEOUT * time.Second)
}
conn, err := net.DialTimeout("tcp", os.Args[1], timeout)
if err != nil {
fmt.Println("Error connecting: ", err.Error())
return
}
defer conn.Close()
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
go func() {
fmt.Println("wait ctrl-c")
for _ = range c {
fmt.Println("close on ctrl-c")
conn.Close()
}
}()
// code added.
go func() {
for {
message, err := bufio.NewReader(conn).ReadString('\n')
if err == io.EOF {
fmt.Print(message)
break
} else if err != nil {
fmt.Println("Error remote reading: ", err.Error())
break
} else {
fmt.Print(message)
}
}
conn.Close()
os.Exit(0)
}()
for {
message, err := bufio.NewReader(os.Stdin).ReadString('\n')
if err == io.EOF {
fmt.Fprint(conn, message)
break
} else if err != nil {
fmt.Println("Error reading: ", err.Error())
break
} else {
fmt.Fprintf(conn, message)
}
}
}
func usage(filename string) string {
return fmt.Sprintf("Usage: %s <address (ex. localhost:8016, google.com:http, [2001:db8::1]:http, etc.)> [timeout (ex. 10s, 300ms, etc.)]", filename)
}
我现在正在使用Windows 7。问题是什么,我该如何解决?
你可以在这里找到服务器端和客户端代码:
https://gist.github.com/programus/52591a97def30df9dc81
英文:
I am trying to build a very simple tcp server/client. And I want the program could close the connection when it is interrupted by ctrl-c.
If I only send message or only receive message in the main thread, everything works just fine.
Here is the code for the client side.
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"net"
"bufio"
"io"
"time"
)
const (
TIMEOUT = 10
)
func main() {
if len(os.Args) < 2 {
fmt.Println(usage(os.Args[0]))
return
}
var timeout time.Duration
if len(os.Args) > 2 {
timeout, _ = time.ParseDuration(os.Args[2])
}
if timeout == 0 {
timeout = time.Duration(TIMEOUT * time.Second)
}
conn, err := net.DialTimeout("tcp", os.Args[1], timeout)
if err != nil {
fmt.Println("Error connecting: ", err.Error())
return
}
defer conn.Close()
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
go func() {
fmt.Println("wait ctrl-c")
for _ = range c {
fmt.Println("close on ctrl-c")
conn.Close()
}
}()
for {
message, err := bufio.NewReader(os.Stdin).ReadString('\n')
if err == io.EOF {
fmt.Fprint(conn, message)
break
} else if err != nil {
fmt.Println("Error reading: ", err.Error())
break
} else {
fmt.Fprintf(conn, message)
}
}
}
func usage(filename string) string {
return fmt.Sprintf("Usage: %s <address (ex. localhost:8016, google.com:http, [2001:db8::1]:http, etc.)> [timeout (ex. 10s, 300ms, etc.)]", filename)
}
But after I added the code that just print what it received and run it in another thread, the ctrl-c handler does not work.
This is the code:
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"net"
"bufio"
"io"
"time"
)
const (
TIMEOUT = 10
)
func main() {
if len(os.Args) < 2 {
fmt.Println(usage(os.Args[0]))
return
}
var timeout time.Duration
if len(os.Args) > 2 {
timeout, _ = time.ParseDuration(os.Args[2])
}
if timeout == 0 {
timeout = time.Duration(TIMEOUT * time.Second)
}
conn, err := net.DialTimeout("tcp", os.Args[1], timeout)
if err != nil {
fmt.Println("Error connecting: ", err.Error())
return
}
defer conn.Close()
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
go func() {
fmt.Println("wait ctrl-c")
for _ = range c {
fmt.Println("close on ctrl-c")
conn.Close()
}
}()
// code added.
go func() {
for {
message, err := bufio.NewReader(conn).ReadString('\n')
if err == io.EOF {
fmt.Print(message)
break
} else if err != nil {
fmt.Println("Error remote reading: ", err.Error())
break
} else {
fmt.Print(message)
}
}
conn.Close()
os.Exit(0)
}()
for {
message, err := bufio.NewReader(os.Stdin).ReadString('\n')
if err == io.EOF {
fmt.Fprint(conn, message)
break
} else if err != nil {
fmt.Println("Error reading: ", err.Error())
break
} else {
fmt.Fprintf(conn, message)
}
}
}
func usage(filename string) string {
return fmt.Sprintf("Usage: %s <address (ex. localhost:8016, google.com:http, [2001:db8::1]:http, etc.)> [timeout (ex. 10s, 300ms, etc.)]", filename)
}
I am working on Windows 7 now.
What is the problem and how can I solve it?
You can find both server side and client side code here:
https://gist.github.com/programus/52591a97def30df9dc81
答案1
得分: 0
我进行了更多的调查,发现问题的关键不是线程,而是信号处理程序是否有足够的时间运行。
我编写了一个简单的程序,在Windows 7下重现了这个问题,并找到了解决方案。
package main
import (
"fmt"
"io"
"os"
"os/signal"
)
func main() {
c := make(chan os.Signal, 1)
//q := make(chan bool, 1)
signal.Notify(c, os.Interrupt, os.Kill)
go func() {
sig := <-c
fmt.Println(sig)
//close(q)
}()
count, err := io.Copy(os.Stdout, os.Stdin)
fmt.Println(count, err)
//select {
//case <-q:
//}
}
你会发现有时信号可能不会被打印出来。但是当我取消注释与q
通道相关的代码后,它总是正常工作。
所以我认为这可能是因为程序退出得太快,没有足够的时间让信号处理程序运行。
希望这个问答能帮助其他遇到同样问题的人。
另外,我解决了简单的TCP服务器/客户端问题,并更新了代码片段。
https://gist.github.com/programus/52591a97def30df9dc81
英文:
I did some more investigation and I found that the key point of the problem is not the threads but whether there is enough time for signal handler running.
I made a simple program to re-produce the problem under Windows 7 as well as the solution.
package main
import (
"fmt"
"io"
"os"
"os/signal"
)
func main() {
c := make(chan os.Signal, 1)
//q := make(chan bool, 1)
signal.Notify(c, os.Interrupt, os.Kill)
go func() {
sig := <- c
fmt.Println(sig)
//close(q)
}()
count, err := io.Copy(os.Stdout, os.Stdin)
fmt.Println(count, err)
//select {
//case <- q:
//}
}
You will find the signal might not be printed out sometime. But after I uncommented the q
channel related code. It always works.
So I think it would because the program quit too quickly to let the signal handler run.
Hope this Q&A could help others who has the same problem.
Also, I worked out a solution for the simple tcp server/client and updated the gist.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论