• 请不要在回答技术问题时复制粘贴 AI 生成的内容
lauix
V2EX  ›  程序员

GO 如何实现 页面非阻塞?

  •  
  •   lauix · Jun 20, 2018 · 8943 views
    This topic created in 2888 days ago, the information mentioned may be changed or developed.
    如题

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

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

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

    研究了 channel 还是不行

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

    基于 web 框架 iris 开发研究测试。
    Supplement 1  ·  Jun 20, 2018
    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 再执行,不是并发执行)

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

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

    @feiyuanqiu
    看了看,一样不能实现 页面非阻塞
    nazor
        7
    nazor  
       Jun 20, 2018 via iPhone
    其实我更好奇你写出的阻塞代码。
    looplj
        8
    looplj  
       Jun 20, 2018
    我也挺好奇的。贴出你的代码看看。
    LT
        9
    LT  
       Jun 20, 2018
    其实我更好奇你写出的阻塞代码。每个 http 请求本来就是独立的啊
    mritd
        10
    mritd  
       Jun 20, 2018
    Talk is cheap, show me the code!
    alexsunxl
        11
    alexsunxl  
       Jun 20, 2018
    非常明确, 是你的代码有问题.. 贴出来吧
    lauix
        12
    lauix  
    OP
       Jun 20, 2018
    @nazor
    @ZSeptember
    @LT
    @alexsunxl
    @mritd

    看 追加的内容
    LT
        13
    LT  
       Jun 20, 2018
    time.sleep 是主线程休眠,你要模拟延迟响应应该写盗 go func(){}里面
    LT
        14
    LT  
       Jun 20, 2018
    ```
    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  
       Jun 20, 2018
    是并发的啊,看控制台输出:
    start
    start
    end
    end
    LT
        16
    LT  
       Jun 20, 2018
    mkeith
        17
    mkeith  
       Jun 20, 2018   ❤️ 1
    @flyingnn 你和楼主分别是什么操作系统啊?
    tysx
        18
    tysx  
       Jun 20, 2018
    mark 一下,好奇你是怎么做到的
    march1993
        19
    march1993  
       Jun 20, 2018   ❤️ 2
    你看看右下角图的 URL 是什么鬼……
    dishonest
        20
    dishonest  
       Jun 20, 2018
    @LT time.Sleep 是 pause current goroutine。

    iris 没用过,或许是 iris 的问题?
    flyingnn
        21
    flyingnn  
       Jun 20, 2018
    @mkeith 我的是 WIN10:Microsoft Windows [版本 10.0.16299.431]
    dishonest
        22
    dishonest  
       Jun 20, 2018
    @march1993 哈。。。
    march1993
        23
    march1993  
       Jun 20, 2018   ❤️ 1
    @dishonest 右下角的 chrome 的 URL 是错的……
    LT
        24
    LT  
       Jun 20, 2018
    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  
       Jun 20, 2018
    @dishonest 看这个 issue 解释 kataras/iris/issues/463 然后,测试下 上一条回复的代码
    march1993
        26
    march1993  
       Jun 20, 2018
    @dishonest 啊 我 sb 了…没问题
    lauix
        27
    lauix  
    OP
       Jun 20, 2018
    @LT 用 go func 那是异步了,我想要返回异步里的内容 怎么办?

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

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

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

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

    iris:





    echo:





    gen:



    looplj
        34
    looplj  
       Jun 20, 2018   ❤️ 5
    别用浏览器,用 curl 就不会这样了。
    looplj
        35
    looplj  
       Jun 20, 2018   ❤️ 1
    gamexg
        36
    gamexg  
       Jun 20, 2018
    可以复现,甚至标准库 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  
       Jun 20, 2018
    我去,楼上破案了,坑了
    gamexg
        38
    gamexg  
       Jun 20, 2018
    @ZSeptember #34 测试后的确时浏览器的问题,procexp 显示只有 1 个连接...
    yanhejihe
        39
    yanhejihe  
       Jun 20, 2018
    这真是奇怪,目前不清楚原因,等破案。
    nazor
        40
    nazor  
       Jun 20, 2018 via iPhone
    用不同浏览器同时请求,出现这个问题可能是因为 goroutine 是针对 tcp 连接的
    wei193
        41
    wei193  
       Jun 20, 2018
    13 楼上不是说了吗? time.Sleep 的锅
    yanhejihe
        42
    yanhejihe  
       Jun 20, 2018
    哇,在我测试 demo 过程就破案了,应该就是浏览器的问题。
    wei193
        43
    wei193  
       Jun 20, 2018
    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  
       Jun 20, 2018
    @wei193 回复 43 楼 测试有误, 应该是 goroutine 针对连接的 因为将上面的代码修改 time.Sleep 一样是并发
    elvodn
        45
    elvodn  
       Jun 20, 2018
    ``` 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  
       Jun 20, 2018   ❤️ 6
    chrome 的锅。打开 console,看下请求时间主要在 stalled 上,由于两次请求是同一个资源,chrome 加了一把锁,导致同步请求。如果把 console 里面的 disable cache 勾上就没事了。
    见这篇文章 http://fex.baidu.com/blog/2015/01/chrome-stalled-problem-resolving-process/
    Shakeitin
        47
    Shakeitin  
       Jun 20, 2018

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

    Chrome 总有些奇怪的骚操作
    Reficul
        53
    Reficul  
       Jun 20, 2018   ❤️ 1
    Go 的 http 服务器本来就是一个请求一个 goroute 的。想写出阻塞的反而有点难度。
    fan123199
        54
    fan123199  
       Jun 20, 2018
    @Reficul 我关注这个问题,就是我前端时间需要用阻塞来处理一个问题。 后来用了 mutex lock 来解决。想不到是个 chrome 还有这种问题。还有一个现象,如果你在 chrome 打开这个网页,然后点 F5 刷新,就不会阻塞。
    karllynn
        55
    karllynn  
       Jun 20, 2018
    楼主这个问题看得我一愣一愣的
    ToT
        56
    ToT  
       Jun 20, 2018
    @ZSeptember 非常有用的链接,居然是第一次知道这个大牛。
    RubyJack
        57
    RubyJack  
       Jun 20, 2018
    1. time.Sleep 当然不是主线程阻塞
    2. 比起锁,感觉像是 http 持久连接带来的问题
    heimeil
        58
    heimeil  
       Jun 20, 2018
    同时请求两个一样的 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  
       Jun 20, 2018
    如果是浏览器问题,那为什么题主使用 tornado 却没有复现此问题?
    auin
        60
    auin  
       Jun 21, 2018
    厉害了,我第一次发现浏览器会串化相同请求。 👍
    lauix
        62
    lauix  
    OP
       Jun 21, 2018
    @ZSeptember 确实是浏览器的问题,感谢!
    CloudnuY
        63
    CloudnuY  
       Jun 21, 2018
    亏我以前抢购还开好几个 tab ……
    fcten
        64
    fcten  
       Jun 21, 2018
    这个策略是很正常的。因为任何一个 GET 请求都是有可能被缓存的,所以并发执行 GET 往往是不必要的。一旦第一个 GET 请求返回并且允许缓存,后续 GET 请求都不必再执行。
    这个策略主要是为了优化静态资源的加载。
    dishonest
        65
    dishonest  
       Jun 21, 2018
    学习了~
    xiadada
        66
    xiadada  
       Jun 21, 2018
    我用原生的 go http 测试了一下, get 同一个地址,确实是串行化的. 这是 Chrome 的问题. 我猜想没有一个 go server 框架在处理 request 的时候回串行处理. 所以请不要在 handler 方法里写什么 go func(){} 还有人不控制 go 的退出结果,后台裸跑 go,,更傻)

    解决的办法很简单, get 地址可以变一变嘛, 请求里塞一个时间戳 /12312 /546 都看成同一个东西就好了.
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   2950 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 186ms · UTC 11:28 · PVG 19:28 · LAX 04:28 · JFK 07:28
    ♥ Do have faith in what you're doing.