在Go的HTTP处理程序中使用goroutine和通道会导致ResponseWriter被阻塞。

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

Using goroutine and channel in a go http handler made the ResponseWriter blocked

问题

以下是翻译好的代码:

package main

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

var ch chan bool

func testTimer1() {
	go func() {
		log.Println("test timer 1")
		ch <- true
	}()
}

func timer1() {
	timer1 := time.NewTicker(2 * time.Second)
	select {
	case <-timer1.C:
		testTimer1()
	}
}

func myhandler(w http.ResponseWriter, r *http.Request) {
	for {
		go timer1()

		a := <-ch
		log.Println("get a:", a)
		fmt.Fprintf(w, "hello world!!!!", a)
	}

	log.Println("test for break")
}

func main() {
	ch = make(chan bool)
	http.HandleFunc("/", myhandler)
	http.ListenAndServe(":8080", nil)
}

我写了上面的代码,在myhandler函数中加入了一个通道,当定时任务执行时,通道会被赋予一个布尔值数据。

然后我从通道中获取数据,并将"hello world"写入HTTP响应写入器。

但是我发现客户端无法接收到"hello world",写入器一直被阻塞!

有人了解这个问题吗?

在我的命令行中运行的截图如下:
图片描述

图片描述

英文:
package main

import (
	&quot;fmt&quot;
	&quot;log&quot;
	&quot;net/http&quot;
	&quot;time&quot;
)

var ch chan bool

func testTimer1() {
	go func() {
		log.Println(&quot;test timer 1&quot;)
		ch &lt;- true
	}()

}

func timer1() {
	timer1 := time.NewTicker(2 * time.Second)
	select {
	case &lt;-timer1.C:
		testTimer1()
	}

}

func myhandler(w http.ResponseWriter, r *http.Request) {
	for {
		go timer1()

		a := &lt;-ch
		log.Println(&quot;get a: &quot;, a)
		fmt.Fprintf(w, &quot;hello world!!!!&quot;, a)
	}

	log.Println(&quot;test for break&quot;)
}

func main() {
	ch = make(chan bool)
	http.HandleFunc(&quot;/&quot;, myhandler)
	http.ListenAndServe(&quot;:8080&quot;, nil)
}

I wrote the above code, put a channel into "myhandler", channel will be
given a bool data when the

timer task executed.

then I get the data from channel and write "hello world" into http writer

but I found the client couldn't receive the "hello world", the writer has been blocked!!!!!

Any one knows about this?

looks the running pic on my cmd:
enter image description here

enter image description here

答案1

得分: 1

for循环是一个无限循环,所以向ResponseWriter打印内容并不会被“调度”执行。如果你想要类似长轮询或者长连接的效果,你可以尝试使用这种方法

timer1()函数中也存在泄漏的定时器问题。根据Go文档的说明:

> 停止定时器以释放相关资源。

每次调用go timer1()时,你都会创建一个新的定时器,而且这个定时器从未关闭,所以每次创建新的定时器都会累积。

英文:

The for loop is an infinite loop so printing to the ResponseWriter is not "scheduled" to happen. If you want a comet-like approack (or long-polling URL) you may want to try this method.

There's also a leak of tickers in timer1(). According to the Go Docs:

> Stop the ticker to release associated resources.

You're always creating a new ticker every time you call go timer1() and the ticker is never closed so every new ticker just adds-up.

答案2

得分: 1

#简短回答

避免缓冲

###客户端

使用带有--no-buffer选项的curl命令

curl http://localhost:8080 --no-buffer

###服务器端
在每个fmt.Fprint之后进行刷新

w.(http.Flusher).Flush()

#长回答

实现HTTP流式传输时最大的问题是理解缓冲的影响。缓冲是将读取或写入累积到一个临时固定内存空间的做法。缓冲的优点包括减少读取或写入调用的开销。例如,你可以一次性写入4096KB而不是4096次写入1KB。这意味着你的程序可以创建一个包含4096KB临时数据的写入缓冲区(可以对齐到磁盘块大小),一旦达到空间限制,缓冲区就会被刷新到磁盘上。

上述提到的HTTP组件包括两个组件:服务器(Go服务器)和客户端(Curl)。每个组件都可以具有可调整和不同的缓冲样式和限制。

一个无关的问题,在给定的程序中还有一个问题,即不停止计时器会一直运行以释放相关资源。

这里是一种带有一些修正的实现

代码

package main

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

var ch chan bool

func testTimer1() {
    go func() {
        log.Println("test timer 1")
        ch <- true
    }()

}

func timer1() {
    timer1 := time.NewTicker(2 * time.Second)
	defer timer1.Stop()
    <-timer1.C
    testTimer1()
}

func myhandler(w http.ResponseWriter, r *http.Request) {
    for {
        go timer1()

        a := <-ch
        log.Println("get a: ", a)
        fmt.Fprintf(w, "hello world!!!! - %v", a)
		w.(http.Flusher).Flush()
    }
}

func main() {
    ch = make(chan bool)
    http.HandleFunc("/", myhandler)
    http.ListenAndServe(":8080", nil)
}

Curl

curl http://localhost:8080 --no-buffer
英文:

#Short answer

Avoid buffering

###Client Side

Use curl with --no-buffer set

curl http://localhost:8080 --no-buffer

###Server Side
Flush after every fmt.Fprint

w.(http.Flusher).Flush()

#Long Answer

&nbsp;&nbsp;&nbsp;The biggest problem when implementing HTTP streaming is understanding the effect of buffering. Buffering is the practice of accumulating reads or writes into a temporarily fixed memory space. The advantages of buffering include reducing read or write call overhead. For example, instead of writing 1KB 4096 times, you can just write 4096KB at once. This means your program can create a write buffer holding 4096KB of temporary data (which can be aligned to the disk block size), and once the space limit is reached, the buffer is flushed to disk.

Here the above mentioned HTTP component include two components Server(go server) and Client(Curl).Each one of these components can possess adjustable and varied buffering styles and limits.

An unrelated issue, n the program given it has one more problem ie, not stopping timer always stop the ticker to release associated resources.

Here is an implementation with some corrections

Code

package main

import (
    &quot;fmt&quot;
    &quot;log&quot;
    &quot;net/http&quot;
    &quot;time&quot;
)

var ch chan bool

func testTimer1() {
    go func() {
        log.Println(&quot;test timer 1&quot;)
        ch &lt;- true
    }()

}

func timer1() {
    timer1 := time.NewTicker(2 * time.Second)
	defer timer1.Stop()
    &lt;-timer1.C
    testTimer1()
}

func myhandler(w http.ResponseWriter, r *http.Request) {
    for {
        go timer1()

        a := &lt;-ch
        log.Println(&quot;get a: &quot;, a)
        fmt.Fprintf(w, &quot;hello world!!!! - %v&quot;, a)
		w.(http.Flusher).Flush()
    }
}

func main() {
    ch = make(chan bool)
    http.HandleFunc(&quot;/&quot;, myhandler)
    http.ListenAndServe(&quot;:8080&quot;, nil)
}

Curl

curl http://localhost:8080 --no-buffer 

huangapple
  • 本文由 发表于 2017年3月23日 22:52:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/42979595.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定