为什么当我在Go中添加另一个阻塞线程时,信号处理程序不起作用?

huangapple go评论108阅读模式
英文:

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 (
&quot;fmt&quot;
&quot;os&quot;
&quot;os/signal&quot;
&quot;syscall&quot;
&quot;net&quot;
&quot;bufio&quot;
&quot;io&quot;
&quot;time&quot;
)
const (
TIMEOUT = 10
)
func main() {
if len(os.Args) &lt; 2 {
fmt.Println(usage(os.Args[0]))
return
}
var timeout time.Duration
if len(os.Args) &gt; 2 {
timeout, _ = time.ParseDuration(os.Args[2])
}
if timeout == 0 {
timeout = time.Duration(TIMEOUT * time.Second)
}
conn, err := net.DialTimeout(&quot;tcp&quot;, os.Args[1], timeout)
if err != nil {
fmt.Println(&quot;Error connecting: &quot;, err.Error())
return
}
defer conn.Close()
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
go func() {
fmt.Println(&quot;wait ctrl-c&quot;)
for _ = range c {
fmt.Println(&quot;close on ctrl-c&quot;)
conn.Close()
}
}()
for {
message, err := bufio.NewReader(os.Stdin).ReadString(&#39;\n&#39;)
if err == io.EOF {
fmt.Fprint(conn, message)
break
} else if err != nil {
fmt.Println(&quot;Error reading: &quot;, err.Error())
break
} else {
fmt.Fprintf(conn, message)
}
}
}
func usage(filename string) string {
return fmt.Sprintf(&quot;Usage: %s &lt;address (ex. localhost:8016, google.com:http, [2001:db8::1]:http, etc.)&gt; [timeout (ex. 10s, 300ms, etc.)]&quot;, 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 (
&quot;fmt&quot;
&quot;os&quot;
&quot;os/signal&quot;
&quot;syscall&quot;
&quot;net&quot;
&quot;bufio&quot;
&quot;io&quot;
&quot;time&quot;
)
const (
TIMEOUT = 10
)
func main() {
if len(os.Args) &lt; 2 {
fmt.Println(usage(os.Args[0]))
return
}
var timeout time.Duration
if len(os.Args) &gt; 2 {
timeout, _ = time.ParseDuration(os.Args[2])
}
if timeout == 0 {
timeout = time.Duration(TIMEOUT * time.Second)
}
conn, err := net.DialTimeout(&quot;tcp&quot;, os.Args[1], timeout)
if err != nil {
fmt.Println(&quot;Error connecting: &quot;, err.Error())
return
}
defer conn.Close()
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
go func() {
fmt.Println(&quot;wait ctrl-c&quot;)
for _ = range c {
fmt.Println(&quot;close on ctrl-c&quot;)
conn.Close()
}
}()
// code added.
go func() {
for {
message, err := bufio.NewReader(conn).ReadString(&#39;\n&#39;)
if err == io.EOF {
fmt.Print(message)
break
} else if err != nil {
fmt.Println(&quot;Error remote reading: &quot;, err.Error())
break
} else {
fmt.Print(message)
}
}
conn.Close()
os.Exit(0)
}()
for {
message, err := bufio.NewReader(os.Stdin).ReadString(&#39;\n&#39;)
if err == io.EOF {
fmt.Fprint(conn, message)
break
} else if err != nil {
fmt.Println(&quot;Error reading: &quot;, err.Error())
break
} else {
fmt.Fprintf(conn, message)
}
}
}
func usage(filename string) string {
return fmt.Sprintf(&quot;Usage: %s &lt;address (ex. localhost:8016, google.com:http, [2001:db8::1]:http, etc.)&gt; [timeout (ex. 10s, 300ms, etc.)]&quot;, 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 (
&quot;fmt&quot;
&quot;io&quot;
&quot;os&quot;
&quot;os/signal&quot;
)
func main() {
c := make(chan os.Signal, 1)
//q := make(chan bool, 1)
signal.Notify(c, os.Interrupt, os.Kill)
go func() {
sig := &lt;- c
fmt.Println(sig)
//close(q)
}()
count, err := io.Copy(os.Stdout, os.Stdin)
fmt.Println(count, err)
//select {
//case &lt;- 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.

https://gist.github.com/programus/52591a97def30df9dc81

huangapple
  • 本文由 发表于 2015年9月9日 15:53:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/32473923.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定