V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
lauix
V2EX  ›  程序员

GO 如何实现 页面非阻塞?

  •  
  •   lauix · 2018-06-20 16:27:27 +08:00 · 7863 次点击
    这是一个创建于 2377 天前的主题,其中的信息可能已经有所发展或是发生改变。
    如题

    页面非阻塞,就是一个页面访问要 5 秒,10 人访问不排队,并发进行。

    Nodejs 自带,Python tornado gevent 都可以实现。

    今天研究 golang 发现找不到如何实现。。。

    研究了 channel 还是不行

    问问 v2 的大神们,有解决方法吗? or 是否有 go web 的高性能解决方案。

    基于 web 框架 iris 开发研究测试。
    第 1 条附言  ·  2018-06-20 17:03:07 +08:00
    package main
    
    import (
    	"github.com/kataras/iris"
    	"github.com/kataras/iris/context"
    	"fmt"
    	"time"
    )
    
    func test(ctx context.Context) {
    	fmt.Println("start")
    
    	// 等待 10 秒
    	time.Sleep(time.Duration(10) * time.Second)
    
    	fmt.Println("end")
    
    	ctx.Text("test")
    }
    
    
    func app_init() *iris.Application {
    	// 创建应用
    	app := iris.New()
    
    	// 开启 debug
    	app.Logger().SetLevel("debug")
    
    	// 路由初始化
    	app.Get("/", test)
    
    	return app
    }
    
    func main() {
    	//应用初始化
    	app := app_init()
    
    	//启动 WEB
    	app.Run(iris.Addr(":8080"))
    }
    

    DEMO 测试 ( 1 先执行的,然后 2 再执行,不是并发执行)

    第 2 条附言  ·  2018-06-21 10:14:22 +08:00
    不一一回复了。
    ZSeptember 大牛给了答案,浏览器的问题。
    Chrome 同时打开会阻塞,我使用 Safari 和 Chrome 就是非阻塞了。
    Safari 同时打开也是非阻塞,就是 Chrome 浏览器的问题 。
    为什么出出现这种问题? http://www.laruence.com/2011/07/16/2123.html
    66 条回复    2018-06-21 15:30:10 +08:00
    dishonest
        1
    dishonest  
       2018-06-20 16:31:03 +08:00
    每个请求 go 不是会开一个 goroutine 吗,这就是并发的呀
    gouchaoer
        2
    gouchaoer  
       2018-06-20 16:31:58 +08:00
    你用 go 写的都是非阻塞的
    k9982874
        3
    k9982874  
       2018-06-20 16:34:12 +08:00
    GOMAXPROCS 了解一下?
    feiyuanqiu
        4
    feiyuanqiu  
       2018-06-20 16:34:30 +08:00   ❤️ 1
    可以用 buffered channel,也可以用 select{ default: } 做 Non-Blocking Channel 操作

    https://tour.golang.org/concurrency/6
    lauix
        5
    lauix  
    OP
       2018-06-20 16:35:48 +08:00
    @dishonest
    @gouchaoer
    没有啊,(代码里写了个 10 秒等待) 两个浏览器同时访问一个页面,都是依次排队执行的。第一个浏览器执行完,第二个浏览器才会执行。
    lauix
        6
    lauix  
    OP
       2018-06-20 16:38:47 +08:00
    @k9982874
    这个我知道,并不能解决 页面非阻塞 也就是 event loop

    @feiyuanqiu
    看了看,一样不能实现 页面非阻塞
    nazor
        7
    nazor  
       2018-06-20 16:44:04 +08:00 via iPhone
    其实我更好奇你写出的阻塞代码。
    ZSeptember
        8
    ZSeptember  
       2018-06-20 16:47:05 +08:00
    我也挺好奇的。贴出你的代码看看。
    LT
        9
    LT  
       2018-06-20 16:48:45 +08:00
    其实我更好奇你写出的阻塞代码。每个 http 请求本来就是独立的啊
    mritd
        10
    mritd  
       2018-06-20 16:52:45 +08:00
    Talk is cheap, show me the code!
    alexsunxl
        11
    alexsunxl  
       2018-06-20 16:58:53 +08:00
    非常明确, 是你的代码有问题.. 贴出来吧
    lauix
        12
    lauix  
    OP
       2018-06-20 17:03:30 +08:00
    @nazor
    @ZSeptember
    @LT
    @alexsunxl
    @mritd

    看 追加的内容
    LT
        13
    LT  
       2018-06-20 17:21:08 +08:00
    time.sleep 是主线程休眠,你要模拟延迟响应应该写盗 go func(){}里面
    LT
        14
    LT  
       2018-06-20 17:21:30 +08:00
    ```
    func test(ctx context.Context) {
    go func(){
    fmt.Println("start")

    // 等待 10 秒
    time.Sleep(time.Duration(10) * time.Second)

    fmt.Println("end")

    ctx.Text("test")
    }()
    }

    ```
    flyingnn
        15
    flyingnn  
       2018-06-20 17:26:17 +08:00
    是并发的啊,看控制台输出:
    start
    start
    end
    end
    LT
        16
    LT  
       2018-06-20 17:30:43 +08:00
    mkeith
        17
    mkeith  
       2018-06-20 17:31:16 +08:00   ❤️ 1
    @flyingnn 你和楼主分别是什么操作系统啊?
    tysx
        18
    tysx  
       2018-06-20 17:32:33 +08:00
    mark 一下,好奇你是怎么做到的
    march1993
        19
    march1993  
       2018-06-20 17:33:11 +08:00   ❤️ 2
    你看看右下角图的 URL 是什么鬼……
    dishonest
        20
    dishonest  
       2018-06-20 17:33:35 +08:00
    @LT time.Sleep 是 pause current goroutine。

    iris 没用过,或许是 iris 的问题?
    flyingnn
        21
    flyingnn  
       2018-06-20 17:34:08 +08:00
    @mkeith 我的是 WIN10:Microsoft Windows [版本 10.0.16299.431]
    dishonest
        22
    dishonest  
       2018-06-20 17:34:13 +08:00
    @march1993 哈。。。
    march1993
        23
    march1993  
       2018-06-20 17:36:08 +08:00   ❤️ 1
    @dishonest 右下角的 chrome 的 URL 是错的……
    LT
        24
    LT  
       2018-06-20 17:36:32 +08:00
    func test(ctx context.Context) {
    c := make(chan int)
    fmt.Println("start")
    go func(){
    // 等待 10 秒
    time.Sleep(time.Duration(5) * time.Second)
    c <- 0
    }()
    <- c
    fmt.Println("end")
    ctx.Text("test")
    }
    LT
        25
    LT  
       2018-06-20 17:37:31 +08:00
    @dishonest 看这个 issue 解释 kataras/iris/issues/463 然后,测试下 上一条回复的代码
    march1993
        26
    march1993  
       2018-06-20 17:39:22 +08:00
    @dishonest 啊 我 sb 了…没问题
    lauix
        27
    lauix  
    OP
       2018-06-20 17:43:20 +08:00
    @LT 用 go func 那是异步了,我想要返回异步里的内容 怎么办?

    @flyingnn 你为什么可以?
    @march1993 浏览器的推荐,URL 是对的
    @dishonest 应该和框架没关系吧,我有空去试试 gin

    @LT 看了你第二个回复,这样执行没有问题,可以取到数据,但是还是阻塞的。我都试过了,没有办法才来 V2 发帖了。
    LT
        28
    LT  
       2018-06-20 17:45:54 +08:00
    没有阻塞额。。。直接在 go func 外层定义变量, 里层接收值就可以了,
    lauix
        29
    lauix  
    OP
       2018-06-20 17:53:01 +08:00
    @LT 我复制你的代码是阻塞的,我的系统 MAC 应该和系统没有关系。你怎么调试的?
    myyou
        30
    myyou  
       2018-06-20 18:07:40 +08:00
    我用 gin 框架,和楼主一样方式测试,没有楼主这个问题

    ![code]( )
    ![log]( )

    是不是 iris 框架有问题?
    janxin
        31
    janxin  
       2018-06-20 18:17:10 +08:00
    破案了,lz 你第二个浏览器网址打错了
    aisk
        32
    aisk  
       2018-06-20 18:25:41 +08:00
    是不是用的比较老的 GO 版本?
    AlphaTr
        33
    AlphaTr  
       2018-06-20 18:34:09 +08:00
    测试了 gen,echo 和 iris 三个框架,都复现了楼主的问题,go version go1.10.2 darwin/amd64

    iris:





    echo:





    gen:



    ZSeptember
        34
    ZSeptember  
       2018-06-20 18:41:01 +08:00   ❤️ 5
    别用浏览器,用 curl 就不会这样了。
    ZSeptember
        35
    ZSeptember  
       2018-06-20 18:42:07 +08:00   ❤️ 1
    gamexg
        36
    gamexg  
       2018-06-20 18:44:37 +08:00
    可以复现,甚至标准库 http 实现也可以复现。

    ```

    package main

    import (
    "fmt"
    "net/http"
    "time"
    )

    func handler(w http.ResponseWriter, req *http.Request) {
    w.Header().Set("Content-Type", "text/plain")

    fmt.Println("start")
    time.Sleep(10 * time.Second)
    fmt.Println("end")
    }

    func main() {

    http.HandleFunc("/", handler)
    http.ListenAndServe(":8088", nil)
    }


    ```

    同时打开 3 个页面输出:
    start
    end
    start
    start
    end
    start
    end
    start
    end
    start
    end
    end
    AlphaTr
        37
    AlphaTr  
       2018-06-20 18:44:39 +08:00
    我去,楼上破案了,坑了
    gamexg
        38
    gamexg  
       2018-06-20 18:47:52 +08:00
    @ZSeptember #34 测试后的确时浏览器的问题,procexp 显示只有 1 个连接...
    yanhejihe
        39
    yanhejihe  
       2018-06-20 19:03:44 +08:00
    这真是奇怪,目前不清楚原因,等破案。
    nazor
        40
    nazor  
       2018-06-20 19:04:42 +08:00 via iPhone
    用不同浏览器同时请求,出现这个问题可能是因为 goroutine 是针对 tcp 连接的
    wei193
        41
    wei193  
       2018-06-20 19:05:47 +08:00
    13 楼上不是说了吗? time.Sleep 的锅
    yanhejihe
        42
    yanhejihe  
       2018-06-20 19:07:12 +08:00
    哇,在我测试 demo 过程就破案了,应该就是浏览器的问题。
    wei193
        43
    wei193  
       2018-06-20 19:11:50 +08:00
    switch r.URL.Path {
    case "/":

    log.Println(1)
    s.Lock()
    // time.Sleep
    log.Println(2)
    fmt.Fprintf(w, "hello word: %d\n", time.Now().Unix())

    default:
    log.Println(1)
    s.Unlock()
    // time.Sleep
    log.Println(2)
    fmt.Fprintf(w, "hello word: %d\n", time.Now().Unix())
    }

    以上代码可以实现并发 所以我觉得是 time.Sleep 问题,测试环境 go version go1.9 darwin/amd64
    wei193
        44
    wei193  
       2018-06-20 19:16:02 +08:00
    @wei193 回复 43 楼 测试有误, 应该是 goroutine 针对连接的 因为将上面的代码修改 time.Sleep 一样是并发
    elvodn
        45
    elvodn  
       2018-06-20 19:19:42 +08:00
    ``` go
    package main

    import (
    "fmt"
    "net/http"
    "time"
    )

    func handler(w http.ResponseWriter, req *http.Request) {
    w.Header().Set("Content-Type", "text/plain")

    fmt.Printf("[%v] start %v %v \n", time.Now(), req.RemoteAddr, req.URL)
    time.Sleep(10 * time.Second)
    fmt.Printf("[%v] end %v %v\n", time.Now(), req.RemoteAddr, req.URL)
    }

    func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8088", nil)
    }

    ```
    1.10.3 没问题啊, 浏览器内多个请求是阻塞的
    icexin
        46
    icexin  
       2018-06-20 19:41:43 +08:00   ❤️ 6
    chrome 的锅。打开 console,看下请求时间主要在 stalled 上,由于两次请求是同一个资源,chrome 加了一把锁,导致同步请求。如果把 console 里面的 disable cache 勾上就没事了。
    见这篇文章 http://fex.baidu.com/blog/2015/01/chrome-stalled-problem-resolving-process/
    Shakeitin
        47
    Shakeitin  
       2018-06-20 19:44:59 +08:00

    在 34 楼之后居然能再续十楼,惊了
    ps: 两边时钟不准,勿细究
    pathbox
        48
    pathbox  
       2018-06-20 20:01:03 +08:00 via iPhone
    @icexin 可以理解为同一个 Chrome 访问同一个资源加锁吗?不同的 Chrome 是不会加锁的吧?
    icexin
        49
    icexin  
       2018-06-20 20:15:52 +08:00
    看着是这样的,我一个开隐身,一个不开就没问题
    iceheart
        50
    iceheart  
       2018-06-20 20:30:23 +08:00 via Android
    pipeline
    scnace
        51
    scnace  
       2018-06-20 20:48:46 +08:00 via Android
    你们真的不是写 go test 测试 但是去用浏览器试这个问题的吗 hhh
    rrfeng
        52
    rrfeng  
       2018-06-20 20:57:07 +08:00
    果然是浏览器的问题,跟我预想的差不多。

    Chrome 总有些奇怪的骚操作
    Reficul
        53
    Reficul  
       2018-06-20 21:07:54 +08:00   ❤️ 1
    Go 的 http 服务器本来就是一个请求一个 goroute 的。想写出阻塞的反而有点难度。
    fan123199
        54
    fan123199  
       2018-06-20 21:44:41 +08:00
    @Reficul 我关注这个问题,就是我前端时间需要用阻塞来处理一个问题。 后来用了 mutex lock 来解决。想不到是个 chrome 还有这种问题。还有一个现象,如果你在 chrome 打开这个网页,然后点 F5 刷新,就不会阻塞。
    karllynn
        55
    karllynn  
       2018-06-20 21:46:10 +08:00
    楼主这个问题看得我一愣一愣的
    ToT
        56
    ToT  
       2018-06-20 21:55:00 +08:00
    @ZSeptember 非常有用的链接,居然是第一次知道这个大牛。
    RubyJack
        57
    RubyJack  
       2018-06-20 22:20:40 +08:00
    1. time.Sleep 当然不是主线程阻塞
    2. 比起锁,感觉像是 http 持久连接带来的问题
    heimeil
        58
    heimeil  
       2018-06-20 22:43:54 +08:00
    同时请求两个一样的 URL,Chrome 会等待第一个请求返回头部信息里的缓存控制规则,第二次请求会再带上缓存规则(如果有的话)在请求头里面,这是 Chrome 的缓存控制优化机制,节省服务器资源,而两个不同的 URL 同时请求就没这种问题,比如 /foo 和 /bar。

    可以了解一下 HTTP 缓存:
    https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=zh-cn
    lazyago
        59
    lazyago  
       2018-06-20 23:24:31 +08:00
    如果是浏览器问题,那为什么题主使用 tornado 却没有复现此问题?
    liuxey
        60
    liuxey  
       2018-06-21 09:14:11 +08:00
    厉害了,我第一次发现浏览器会串化相同请求。 👍
    lauix
        62
    lauix  
    OP
       2018-06-21 10:11:17 +08:00
    @ZSeptember 确实是浏览器的问题,感谢!
    CloudnuY
        63
    CloudnuY  
       2018-06-21 10:53:16 +08:00
    亏我以前抢购还开好几个 tab ……
    fcten
        64
    fcten  
       2018-06-21 11:03:42 +08:00
    这个策略是很正常的。因为任何一个 GET 请求都是有可能被缓存的,所以并发执行 GET 往往是不必要的。一旦第一个 GET 请求返回并且允许缓存,后续 GET 请求都不必再执行。
    这个策略主要是为了优化静态资源的加载。
    dishonest
        65
    dishonest  
       2018-06-21 11:23:57 +08:00
    学习了~
    xiadada
        66
    xiadada  
       2018-06-21 15:30:10 +08:00
    我用原生的 go http 测试了一下, get 同一个地址,确实是串行化的. 这是 Chrome 的问题. 我猜想没有一个 go server 框架在处理 request 的时候回串行处理. 所以请不要在 handler 方法里写什么 go func(){} 还有人不控制 go 的退出结果,后台裸跑 go,,更傻)

    解决的办法很简单, get 地址可以变一变嘛, 请求里塞一个时间戳 /12312 /546 都看成同一个东西就好了.
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   968 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 19:14 · PVG 03:14 · LAX 11:14 · JFK 14:14
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.