许多语言都有自己的高级非阻塞HTTP客户端,例如python的aiohttp。即,它们发出HTTP请求;不要等待回应;当响应到达时,它们会进行某种回调。
我的问题是
答案 0 :(得分:2)
其他语言具有这样的功能,因为当它们阻塞等待的请求时,它们就会阻塞正在使用的线程。 Java,Python或NodeJS就是这种情况。因此,为了使它们有用,开发人员需要使用回调实现这种长期存在的阻塞操作。造成这种情况的根本原因是使用了C库,该C库阻止了输入输出操作中的线程。
Go不使用C库(仅在某些情况下可以使用,但可以将其关闭),并自行进行系统调用。在执行此操作时,执行当前goroutine的线程将其停放并执行另一个goroutine。因此,您可以拥有大量被阻塞的goroutine,而不会耗尽线程。就内存而言,Goroutine很便宜,线程是操作系统实体。
在Go中使用goroutines更好。由于上述原因,无需创建异步客户端。
要在Java中进行比较,您很快就会遇到多个线程。下一步将是合并它们,因为它们很昂贵。池化意味着限制并发性。
答案 1 :(得分:1)
正如其他人所说,goroutines是行之有效的方法(双关语意)。
最小示例:
type nonBlocking struct {
Response *http.Response
Error error
}
const numRequests = 2
func main() {
nb := make(chan nonBlocking, numRequests)
wg := &sync.WaitGroup{}
for i := 0; i < numRequests; i++ {
wg.Add(1)
go Request(nb)
}
go HandleResponse(nb, wg)
wg.Wait()
}
func Request(nb chan nonBlocking) {
resp, err := http.Get("http://example.com")
nb <- nonBlocking{
Response: resp,
Error: err,
}
}
func HandleResponse(nb chan nonBlocking, wg *sync.WaitGroup) {
for get := range nb {
if get.Error != nil {
log.Println(get.Error)
} else {
log.Println(get.Response.Status)
}
wg.Done()
}
}
答案 2 :(得分:0)
Yip,内置于标准库中,只是开箱即用的简单函数调用无法使用。
以这个例子
package main
import (
"flag"
"log"
"net/http"
"sync"
"time"
)
var url string
var timeout time.Duration
func init() {
flag.StringVar(&url, "url", "http://www.stackoverflow.com", "url to GET")
flag.DurationVar(&timeout, "timeout", 5*time.Second, "timeout for the GET operation")
}
func main() {
flag.Parse()
// We use the channel as our means to
// hand the response over
rc := make(chan *http.Response)
// We need a waitgroup because all goroutines exit when main exits
var wg sync.WaitGroup
// We are spinning up an async request
// Increment the counter for our WaitGroup.
// What we are basically doing here is to tell the WaitGroup
// "Hey, there is one more task you have to wait for!"
wg.Add(1)
go func() {
// Notify the WaitGroup that one task is done as soon
// as we exit the goroutine.
defer wg.Done()
log.Printf("Doing GET request on \"%s\"", url)
resp, err := http.Get(url)
if err != nil {
log.Printf("GET for %s: %s", url, err)
}
// We send the reponse downstream
rc <- resp
// Now, the goroutine exits, the defered call to wg.Done()
// is executed.
}()
// And here we do our async processing.
// Note that you could have done the processing in the first goroutine
// as well, since http.Get would be a blocking operation and any subsequent
// code in the goroutine would have been excuted only after the Get returned.
// However, I put te processing into its own goroutine for demonstration purposes.
wg.Add(1)
go func() {
// As above
defer wg.Done()
log.Println("Doing something else")
// Setting up a timer for a timeout.
// Note that this could be done using a request with a context, as well.
to := time.NewTimer(timeout).C
select {
case <-to:
log.Println("Timeout reached")
// Exiting the goroutine, the deferred call to wg.Done is executed
return
case r := <-rc:
if r == nil {
log.Printf("Got no useful response from GETting \"%s\"", url)
// Exiting the goroutine, the deferred call to wg.Done is executed
return
}
log.Printf("Got response with status code %d (%s)", r.StatusCode, r.Status)
log.Printf("Now I can do something useful with the response")
}
}()
// Now we have set up all of our tasks,
// we are waiting until all of them are done...
wg.Wait()
log.Println("All tasks done, exiting")
}
如果您仔细观察一下,我们将提供所有构建块,以使GET网址和异步处理响应成为可能。我们可以开始对此进行抽象:
package main
import (
"flag"
"log"
"net/http"
"time"
)
var url string
var timeout time.Duration
func init() {
flag.StringVar(&url, "url", "http://www.stackoverflow.com", "url to GET")
flag.DurationVar(&timeout, "timeout", 5*time.Second, "timeout for the GET operation")
}
type callbackFunc func(*http.Response, error) error
func getWithCallBack(u string, callback callbackFunc) chan error {
// We create a channel which we can use to notify the caller of the
// result of the callback.
c := make(chan error)
go func() {
c <- callback(http.Get(u))
}()
return c
}
func main() {
flag.Parse()
c := getWithCallBack(url, func(resp *http.Response, err error) error {
if err != nil {
// Doing something useful with the err.
// Add additional cases as needed.
switch err {
case http.ErrNotSupported:
log.Printf("GET not supported for \"%s\"", url)
}
return err
}
log.Printf("GETting \"%s\": Got response with status code %d (%s)", url, resp.StatusCode, resp.Status)
return nil
})
if err := <-c; err != nil {
log.Printf("Error GETting \"%s\": %s", url, err)
}
log.Println("All tasks done, exiting")
}
然后您去(双关语意):GET请求的异步处理。