GO 如何实现 页面非阻塞? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
lauix
V2EX    程序员

GO 如何实现 页面非阻塞?

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

    页面非阻塞,就是一个页面访问要 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
        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
    其实我更好奇你写出的阻塞代码。
    looplj
        8
    looplj  
       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:



    looplj
        34
    looplj  
       2018-06-20 18:41:01 +08:00   5
    别用浏览器,用 curl 就不会这样了。
    looplj
        35
    looplj  
       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     Solana     896 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 20:47 PVG 04:47 LAX 13:47 JFK 16:47
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86