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

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

Why signal handler not work when I added another blocking thread in go?

问题

我正在尝试构建一个非常简单的TCP服务器/客户端,并且希望程序在通过Ctrl-C中断时能够关闭连接。

如果在主线程中只发送消息或只接收消息,一切都正常工作。

这是客户端的代码:

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. "os/signal"
  6. "syscall"
  7. "net"
  8. "bufio"
  9. "io"
  10. "time"
  11. )
  12. const (
  13. TIMEOUT = 10
  14. )
  15. func main() {
  16. if len(os.Args) < 2 {
  17. fmt.Println(usage(os.Args[0]))
  18. return
  19. }
  20. var timeout time.Duration
  21. if len(os.Args) > 2 {
  22. timeout, _ = time.ParseDuration(os.Args[2])
  23. }
  24. if timeout == 0 {
  25. timeout = time.Duration(TIMEOUT * time.Second)
  26. }
  27. conn, err := net.DialTimeout("tcp", os.Args[1], timeout)
  28. if err != nil {
  29. fmt.Println("Error connecting: ", err.Error())
  30. return
  31. }
  32. defer conn.Close()
  33. c := make(chan os.Signal, 1)
  34. signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
  35. go func() {
  36. fmt.Println("wait ctrl-c")
  37. for _ = range c {
  38. fmt.Println("close on ctrl-c")
  39. conn.Close()
  40. }
  41. }()
  42. for {
  43. message, err := bufio.NewReader(os.Stdin).ReadString('\n')
  44. if err == io.EOF {
  45. fmt.Fprint(conn, message)
  46. break
  47. } else if err != nil {
  48. fmt.Println("Error reading: ", err.Error())
  49. break
  50. } else {
  51. fmt.Fprintf(conn, message)
  52. }
  53. }
  54. }
  55. func usage(filename string) string {
  56. return fmt.Sprintf("Usage: %s <address (ex. localhost:8016, google.com:http, [2001:db8::1]:http, etc.)> [timeout (ex. 10s, 300ms, etc.)]", filename)
  57. }

但是,当我添加了打印接收到的消息并在另一个线程中运行时,Ctrl-C处理程序不起作用。

这是代码:

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. "os/signal"
  6. "syscall"
  7. "net"
  8. "bufio"
  9. "io"
  10. "time"
  11. )
  12. const (
  13. TIMEOUT = 10
  14. )
  15. func main() {
  16. if len(os.Args) < 2 {
  17. fmt.Println(usage(os.Args[0]))
  18. return
  19. }
  20. var timeout time.Duration
  21. if len(os.Args) > 2 {
  22. timeout, _ = time.ParseDuration(os.Args[2])
  23. }
  24. if timeout == 0 {
  25. timeout = time.Duration(TIMEOUT * time.Second)
  26. }
  27. conn, err := net.DialTimeout("tcp", os.Args[1], timeout)
  28. if err != nil {
  29. fmt.Println("Error connecting: ", err.Error())
  30. return
  31. }
  32. defer conn.Close()
  33. c := make(chan os.Signal, 1)
  34. signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
  35. go func() {
  36. fmt.Println("wait ctrl-c")
  37. for _ = range c {
  38. fmt.Println("close on ctrl-c")
  39. conn.Close()
  40. }
  41. }()
  42. // code added.
  43. go func() {
  44. for {
  45. message, err := bufio.NewReader(conn).ReadString('\n')
  46. if err == io.EOF {
  47. fmt.Print(message)
  48. break
  49. } else if err != nil {
  50. fmt.Println("Error remote reading: ", err.Error())
  51. break
  52. } else {
  53. fmt.Print(message)
  54. }
  55. }
  56. conn.Close()
  57. os.Exit(0)
  58. }()
  59. for {
  60. message, err := bufio.NewReader(os.Stdin).ReadString('\n')
  61. if err == io.EOF {
  62. fmt.Fprint(conn, message)
  63. break
  64. } else if err != nil {
  65. fmt.Println("Error reading: ", err.Error())
  66. break
  67. } else {
  68. fmt.Fprintf(conn, message)
  69. }
  70. }
  71. }
  72. func usage(filename string) string {
  73. return fmt.Sprintf("Usage: %s <address (ex. localhost:8016, google.com:http, [2001:db8::1]:http, etc.)> [timeout (ex. 10s, 300ms, etc.)]", filename)
  74. }

我现在正在使用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.

  1. package main
  2. import (
  3. &quot;fmt&quot;
  4. &quot;os&quot;
  5. &quot;os/signal&quot;
  6. &quot;syscall&quot;
  7. &quot;net&quot;
  8. &quot;bufio&quot;
  9. &quot;io&quot;
  10. &quot;time&quot;
  11. )
  12. const (
  13. TIMEOUT = 10
  14. )
  15. func main() {
  16. if len(os.Args) &lt; 2 {
  17. fmt.Println(usage(os.Args[0]))
  18. return
  19. }
  20. var timeout time.Duration
  21. if len(os.Args) &gt; 2 {
  22. timeout, _ = time.ParseDuration(os.Args[2])
  23. }
  24. if timeout == 0 {
  25. timeout = time.Duration(TIMEOUT * time.Second)
  26. }
  27. conn, err := net.DialTimeout(&quot;tcp&quot;, os.Args[1], timeout)
  28. if err != nil {
  29. fmt.Println(&quot;Error connecting: &quot;, err.Error())
  30. return
  31. }
  32. defer conn.Close()
  33. c := make(chan os.Signal, 1)
  34. signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
  35. go func() {
  36. fmt.Println(&quot;wait ctrl-c&quot;)
  37. for _ = range c {
  38. fmt.Println(&quot;close on ctrl-c&quot;)
  39. conn.Close()
  40. }
  41. }()
  42. for {
  43. message, err := bufio.NewReader(os.Stdin).ReadString(&#39;\n&#39;)
  44. if err == io.EOF {
  45. fmt.Fprint(conn, message)
  46. break
  47. } else if err != nil {
  48. fmt.Println(&quot;Error reading: &quot;, err.Error())
  49. break
  50. } else {
  51. fmt.Fprintf(conn, message)
  52. }
  53. }
  54. }
  55. func usage(filename string) string {
  56. 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)
  57. }

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:

  1. package main
  2. import (
  3. &quot;fmt&quot;
  4. &quot;os&quot;
  5. &quot;os/signal&quot;
  6. &quot;syscall&quot;
  7. &quot;net&quot;
  8. &quot;bufio&quot;
  9. &quot;io&quot;
  10. &quot;time&quot;
  11. )
  12. const (
  13. TIMEOUT = 10
  14. )
  15. func main() {
  16. if len(os.Args) &lt; 2 {
  17. fmt.Println(usage(os.Args[0]))
  18. return
  19. }
  20. var timeout time.Duration
  21. if len(os.Args) &gt; 2 {
  22. timeout, _ = time.ParseDuration(os.Args[2])
  23. }
  24. if timeout == 0 {
  25. timeout = time.Duration(TIMEOUT * time.Second)
  26. }
  27. conn, err := net.DialTimeout(&quot;tcp&quot;, os.Args[1], timeout)
  28. if err != nil {
  29. fmt.Println(&quot;Error connecting: &quot;, err.Error())
  30. return
  31. }
  32. defer conn.Close()
  33. c := make(chan os.Signal, 1)
  34. signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
  35. go func() {
  36. fmt.Println(&quot;wait ctrl-c&quot;)
  37. for _ = range c {
  38. fmt.Println(&quot;close on ctrl-c&quot;)
  39. conn.Close()
  40. }
  41. }()
  42. // code added.
  43. go func() {
  44. for {
  45. message, err := bufio.NewReader(conn).ReadString(&#39;\n&#39;)
  46. if err == io.EOF {
  47. fmt.Print(message)
  48. break
  49. } else if err != nil {
  50. fmt.Println(&quot;Error remote reading: &quot;, err.Error())
  51. break
  52. } else {
  53. fmt.Print(message)
  54. }
  55. }
  56. conn.Close()
  57. os.Exit(0)
  58. }()
  59. for {
  60. message, err := bufio.NewReader(os.Stdin).ReadString(&#39;\n&#39;)
  61. if err == io.EOF {
  62. fmt.Fprint(conn, message)
  63. break
  64. } else if err != nil {
  65. fmt.Println(&quot;Error reading: &quot;, err.Error())
  66. break
  67. } else {
  68. fmt.Fprintf(conn, message)
  69. }
  70. }
  71. }
  72. func usage(filename string) string {
  73. 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)
  74. }

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下重现了这个问题,并找到了解决方案。

  1. package main
  2. import (
  3. "fmt"
  4. "io"
  5. "os"
  6. "os/signal"
  7. )
  8. func main() {
  9. c := make(chan os.Signal, 1)
  10. //q := make(chan bool, 1)
  11. signal.Notify(c, os.Interrupt, os.Kill)
  12. go func() {
  13. sig := <-c
  14. fmt.Println(sig)
  15. //close(q)
  16. }()
  17. count, err := io.Copy(os.Stdout, os.Stdin)
  18. fmt.Println(count, err)
  19. //select {
  20. //case <-q:
  21. //}
  22. }

你会发现有时信号可能不会被打印出来。但是当我取消注释与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.

  1. package main
  2. import (
  3. &quot;fmt&quot;
  4. &quot;io&quot;
  5. &quot;os&quot;
  6. &quot;os/signal&quot;
  7. )
  8. func main() {
  9. c := make(chan os.Signal, 1)
  10. //q := make(chan bool, 1)
  11. signal.Notify(c, os.Interrupt, os.Kill)
  12. go func() {
  13. sig := &lt;- c
  14. fmt.Println(sig)
  15. //close(q)
  16. }()
  17. count, err := io.Copy(os.Stdout, os.Stdin)
  18. fmt.Println(count, err)
  19. //select {
  20. //case &lt;- q:
  21. //}
  22. }

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:

确定