V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
zzlettle
V2EX  ›  问与答

刚学 golang,学写了个 tcp server 代码, 为什么函数已经返回了,里面的一段 time.afterfunc 代码还能执行,帮我看看到底是什么原因

  •  
  •   zzlettle · 2019-01-17 04:13:45 +08:00 · 2585 次点击
    这是一个创建于 2136 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近开始学习 go,照着教程写了一个简单的 tcp server 的代码,实现了 5 秒客户端还没有任何的数据发送,就自动关闭当前连接。这个地方我用的是 time.afterfunc 来实现的 运行起来也达到了预期,但不知道为什么,明明当前连接已经关闭,处理连接的函数已经返回,但是里面的这个 time.afterfunc 包裹的函数继续执行,真是见鬼了。下面是代码

    package main
    import (
    	"fmt"
    	"net"
    	"time"
    )
    
    func main() {
    	l, err := net.Listen("tcp", ":8888")
    	if err != nil {
    		fmt.Println("listen 报错:", err)
    		return
    	}
    	defer l.Close()
    	fmt.Println("listen ok")
    	var i int
    	for {
    
    		if conn, err := l.Accept(); err != nil {
    			fmt.Println("accept 报错:", err)
    			break
    		} else {
    
    			i++
    			fmt.Printf("%d: 接收到新的连接\n", i)
    			go fmt.Println(handleconntimeout(conn, 5))
    		}
    
    	}
    }
    
    func handleconntimeout(conn net.Conn, timeout int) int {
    	b := make([]byte, 1024)
    	var closetimer *time.Timer
    	f := realfunc(conn)
    	closetimer = time.AfterFunc(time.Duration(timeout)*time.Second, f)
    	defer conn.Close()
    	for n, err := conn.Read(b); err == nil; n, err = conn.Read(b) {
    
    		
    		closetimer.Reset(time.Duration(timeout) * time.Second)
    		fmt.Printf("收到来自 %s 的一共 %d 数量字节\n", conn.RemoteAddr(), n)
    		fmt.Println(string(b[:n]))
    
    	}
    	fmt.Println("连接报错")
    	
    	return 10000
    
    }
    func closeconn(conn net.Conn) {
    	fmt.Println("时间到,关闭连接")
    	conn.Close()
    
    }
    func realfunc(conn net.Conn) func() {
    	return func() {
    		closeconn(conn)
    	}
    
    }
    

    我启动服务端,用客户端连接没有问题,等待 5 秒钟,服务端这边一切如预期的一样, 第一行出现:时间到,关闭连接 第二行出现:连接报错 第三行打印出 handleconntimeout 这个函数的返回值 1000

    诡异的是,我这边如果主动关闭客户端,服务端出现的是 第一行打印出:连接报错 第二行打印出:handleconntimeout 的返回值 1000 我本来以为后面就不应该再继续出现任何东西了 结果过了 5 秒第三行打印出 时间到,连接关闭

    奇怪了,这个明明应该在 handleconntimeout 里面存在的动作,

    time.AfterFunc(time.Duration(timeout)*time.Second, f) 这个延后执行的函数,不是应该随着 handleconntimeout 的返回,不应该继续执行的啊 怎么后来还在继续执行 由于是初学 golang,高手懂的给指点指点。 谢谢

    7 条回复    2019-01-18 00:14:22 +08:00
    yuikns
        1
    yuikns  
       2019-01-17 05:07:21 +08:00
    return 10000 前加上

    closetimer.Stop()
    yuikns
        2
    yuikns  
       2019-01-17 05:11:23 +08:00
    另外,若你只是想要个 timeout,其实还可以这么写:


    func handleconntimeout(conn net.Conn, timeout int) int {
    b := make([]byte, 1024)
    defer conn.Close()
    conn.SetDeadline(time.Now().Add(time.Duration(timeout) * time.Second))
    for n, err := conn.Read(b); err == nil; n, err = conn.Read(b) {
    fmt.Printf("收到来自 %s 的一共 %d 数量字节\n", conn.RemoteAddr(), n)
    fmt.Println(string(b[:n]))
    conn.SetDeadline(time.Now().Add(time.Duration(timeout) * time.Second))
    }
    return 10000
    }
    zzlettle
        3
    zzlettle  
    OP
       2019-01-17 06:22:21 +08:00 via iPad
    @yuikns 是的,我就是最后加上 closetimer.Stop()来避免。但这个还么做,还是没有让我搞清楚,为什么外面的函数已经返回了,里面的还能继续运行。
    yuikns
        4
    yuikns  
       2019-01-17 06:31:35 +08:00
    @zzlettle 因为没有回收啊。

    帮你找个源码:

    ------

    func AfterFunc(d Duration, f func()) *Timer {
    t := &Timer{
    r: runtimeTimer{
    when: when(d),
    f: goFunc,
    arg: f,
    },
    }
    startTimer(&t.r)
    return t
    }

    func goFunc(arg interface{}, seq uintptr) {
    go arg.(func())()
    }

    -----

    closetimer := time.AfterFunc(time.Duration(timeout)*time.Second, f)

    这个就相当于开一个 goroutine, 等到 timer 到了然后执行一下。这个要是回去就把 goroutine 里面的东西全给撤了,那 go 可以废了...
    yuikns
        5
    yuikns  
       2019-01-17 06:42:57 +08:00
    哦,刚才的回复结论没啥问题,说明有问题。

    startTimer 的实现参考这个链接:

    https://golang.org/src/runtime/time.go#L110

    读源码可知它就是在全局 goroutine 里面加上了一个 timer。 上述那个似乎是在暗示当场 go func() 。开个自定义的。但其实不是。它是先检查已有的 timer。若有多个,会共享一个 routine。然后到它的时候,回调执行 go arg(xxx). 这样可以不阻塞别的 timer。
    zzlettle
        6
    zzlettle  
    OP
       2019-01-17 17:42:49 +08:00
    @yuikns 原因应该跟 GO 里面的底层 goroutin 调度有关,应该是官方的有些问题,应该要改进。否则函数返回,里面还继续运行。反正我就是拿来写自己的应用,怎么能搞定怎么来。看源代码绕来绕去的,不到万不得已,真不想看。
    yuikns
        7
    yuikns  
       2019-01-18 00:14:22 +08:00
    @zzlettle 你的意思是

    func Foo() {
    go func() {
    time.Sleep(1 * time.Second)
    fmt.Println("bar")
    }()
    return
    }

    这里面的 bar 应该永远不输出?

    "不到万不得已,真不想看。", "应该是官方的有些问题"。这是 goroutine 一致的逻辑。此处没有特例。用 go 委屈你了,换个语言吧
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1103 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 22:51 · PVG 06:51 · LAX 14:51 · JFK 17:51
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.