在Golang的handler中如何启动goroutine

在Golang的handler中如何启动goroutine 我有一个HTTP处理程序。我希望启动一个Go协程,然后填充响应写入器并将控制权返回给浏览器,但同时保持Go协程的运行。我能够使用通道来保持Go协程运行,但页面无法传递给浏览器,之前的页面只是被挂起,处于等待状态。

func realTimeHandler(writer http.ResponseWriter, request *http.Request) {
	fmt.Println("realTimeHandler")
	log.Println("realTimeHandler")
	p := data.DaemonPage{}
	initializeHandling(&p, request)
	initializeTrackSpecification(&p)
	p.TrackSpecification.Stock.Symbol = getStringValue(&p, "stockSymbol")
	p.RealTime = true

	ch := make(chan bool)

	go finnHubRealTime.Realtime(p.TrackSpecification.Stock.Symbol, ch)

	RenderPage(writer, &p)

	done := <-ch
	log.Println(done)
}

如果没有通道锁,新页面会被渲染,但函数会退出,从而终止了 finnHubRealTime.Realtime 函数的执行。


更多关于在Golang的handler中如何启动goroutine的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

你确定问题不是main函数退出导致的吗?看看这个StackOverflow上的问题。我假设你阻塞了main函数,但以防万一:

Goroutine Termination on Calling Function Return

另外:

之前的页面只是挂起,在等待。

这让我怀疑finnHubRealTime.Realtime是否陷入了无限循环或类似的情况。如果你能展示那里的实现,可能会有所帮助。

更多关于在Golang的handler中如何启动goroutine的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


如果需要对金融数据进行持续实时的监控,使用 socket io 会更加合适。如果想将长时间运行的计算结果分片返回,可以返回分块响应。

kimura

从 Go 服务器发送分块 HTTP 响应

go

kimura 提问于 12:15AM - 06 Nov 14 UTC

我认为这不太优雅,但它能工作。在 main 包中,一个全局变量:

var realTimeGoRoutinesControllerChannel = make(chan *data.DaemonPage)

然后,在 main() 中

	go func() {
		for {
			p := <-realTimeGoRoutinesControllerChannel
			ch := make(chan bool)

			go finnHubRealTime.Realtime(p.TrackSpecification.Stock.Symbol, ch)

		}
	}()

然后在处理程序中:

func realTimeHandler(writer http.ResponseWriter, request *http.Request) {
	fmt.Println("realTimeHandler")
	log.Println("realTimeHandler")
	p := data.DaemonPage{}

	initializeHandling(&p, request)
	initializeTrackSpecification(&p)
	p.TrackSpecification.Stock.Symbol = getStringValue(&p, "stockSymbol")
	p.RealTime = true

	realTimeGoRoutinesControllerChannel <- &p

	RenderPage(writer, &p)

}

感谢您的分享。我目前使用的服务是 Finnhub,它支持 WebSocket,这是我之前从未使用过的技术。生成实时报价的代码如下:

		w, _, err := websocket.DefaultDialer.Dial("wss://ws.finnhub.io?token=TOKEN"XXX, nil)
		if err != nil {
			panic(err)
		}
		defer w.Close()

		symbols := []string{stockSymbol}

		for _, s := range symbols {
			msg, _ := json.Marshal(map[string]interface{}{"type": "subscribe", "symbol": s})
			w.WriteMessage(websocket.TextMessage, msg)
		}

		var msg interface{}

		stock.LatestPrice = -1
		stock.Symbol = stockSymbol

		for doRealTime {
			err := w.ReadJSON(&msg)

之后,我只需要处理 msg 中的报价数据。现在,我正在尝试连接到交易 API,但遗憾的是,它使用的是 OAuth 1.0 协议,这相当麻烦,而且我已经有十多年没接触过它了。开发这个应用所使用的技术,大部分我都是第一次接触,因此我正在学习很多新知识。

在HTTP处理程序中启动goroutine的正确做法是:不要阻塞主处理流程。你的代码中done := <-ch会阻塞,导致响应无法立即发送。以下是修改后的示例:

func realTimeHandler(writer http.ResponseWriter, request *http.Request) {
    fmt.Println("realTimeHandler")
    log.Println("realTimeHandler")
    p := data.DaemonPage{}
    initializeHandling(&p, request)
    initializeTrackSpecification(&p)
    p.TrackSpecification.Stock.Symbol = getStringValue(&p, "stockSymbol")
    p.RealTime = true

    // 启动goroutine但不等待它完成
    go func() {
        // 使用context来处理超时或取消
        ctx := context.Background()
        finnHubRealTime.Realtime(p.TrackSpecification.Stock.Symbol, ctx)
    }()

    // 立即渲染页面并返回响应
    RenderPage(writer, &p)
}

如果你的finnHubRealTime.Realtime函数需要持续运行,可以这样实现:

// 使用全局变量或依赖注入来管理goroutine生命周期
var (
    realtimeWorkers sync.Map
)

func realTimeHandler(writer http.ResponseWriter, request *http.Request) {
    // ... 初始化代码 ...
    
    symbol := p.TrackSpecification.Stock.Symbol
    
    // 检查是否已经为该symbol启动了goroutine
    if _, loaded := realtimeWorkers.LoadOrStore(symbol, true); !loaded {
        // 启动新的goroutine
        go func(sym string) {
            ctx := context.Background()
            finnHubRealTime.Realtime(sym, ctx)
            // 完成后清理
            realtimeWorkers.Delete(sym)
        }(symbol)
    }
    
    RenderPage(writer, &p)
}

如果需要传递数据给goroutine但不想阻塞:

func realTimeHandler(writer http.ResponseWriter, request *http.Request) {
    // ... 初始化代码 ...
    
    // 使用带缓冲的channel避免阻塞
    dataCh := make(chan interface{}, 1)
    
    go func(ch chan interface{}) {
        // 从channel接收数据(非阻塞方式)
        select {
        case data := <-ch:
            // 处理数据
            _ = data
        default:
            // 没有数据时继续执行
        }
        
        // 执行长时间运行的任务
        finnHubRealTime.Realtime(p.TrackSpecification.Stock.Symbol, context.Background())
    }(dataCh)
    
    // 可以发送数据到goroutine(不会阻塞)
    dataCh <- someData
    
    RenderPage(writer, &p)
}

关键点:

  1. 不要在handler中等待goroutine完成
  2. 使用context管理goroutine生命周期
  3. 考虑使用sync.Map或类似结构管理长时间运行的goroutine
  4. 带缓冲的channel可以避免阻塞主流程
回到顶部