1. 资源回收
- 明确协程边界:使用
context
来清晰界定每个协程的生命周期范围。例如在Go语言中,context
作为参数在函数调用链中传递,当上层 context
取消时,所有基于该 context
创建的下层协程都应收到取消信号并及时清理资源。如下代码:
package main
import (
"context"
"fmt"
"time"
)
func worker(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("Worker received cancel signal, cleaning up resources")
// 清理数据库连接、文件句柄等资源
return
default:
// 实际工作逻辑
fmt.Println("Worker is working")
time.Sleep(1 * time.Second)
}
}
}
- 取消父子协程关联:如果一个协程启动了多个子协程,确保在父协程取消时,子协程也能正确取消。在Go中,可通过传递相同的
context
来实现父子协程关联取消。例如:
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
go worker(ctx)
time.Sleep(5 * time.Second)
}
2. 减少不必要开销
- 复用
context
:避免在每个函数调用中都重新创建 context
。尽量使用从上层传递下来的 context
,只有在需要设置新的截止时间、取消逻辑时才创建新的 context
。比如在一个复杂的业务逻辑调用链中:
func processRequest(ctx context.Context) {
subCtx := ctx // 复用上层传递的context
// 调用其他函数并传递subCtx
doSomeWork(subCtx)
// 只有在需要特殊处理时才创建新的context
newCtx, cancel := context.WithTimeout(ctx, 1*time.Second)
defer cancel()
doTimeSensitiveWork(newCtx)
}
- 延迟创建协程:不要过早地启动大量协程,而是根据实际需求动态创建。可以使用工作池(worker pool)模式,在有任务时从池中获取协程,任务完成后协程返回池中,避免频繁创建和销毁协程带来的开销。以Go语言为例,如下代码实现一个简单的工作池:
package main
import (
"context"
"fmt"
"sync"
"time"
)
type Worker struct {
id int
ctx context.Context
wg *sync.WaitGroup
}
func (w *Worker) work() {
defer w.wg.Done()
for {
select {
case <-w.ctx.Done():
fmt.Printf("Worker %d stopped\n", w.id)
return
default:
fmt.Printf("Worker %d is working\n", w.id)
time.Sleep(1 * time.Second)
}
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
var wg sync.WaitGroup
numWorkers := 3
workers := make([]*Worker, numWorkers)
for i := 0; i < numWorkers; i++ {
workers[i] = &Worker{
id: i,
ctx: ctx,
wg: &wg,
}
wg.Add(1)
go workers[i].work()
}
wg.Wait()
}
3. 处理网络超时
- 设置网络操作超时:在进行网络通信时,使用
context
设置超时。在Go语言的标准库 net/http
包中,http.Client
的 Do
方法接受一个 context
,可用于设置请求超时。例如:
package main
import (
"context"
"fmt"
"net/http"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", "http://example.com", nil)
if err != nil {
fmt.Println("Error creating request:", err)
return
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error making request:", err)
return
}
defer resp.Body.Close()
}
- 超时重试机制:结合
context
和重试逻辑,当网络请求超时时,可以根据业务需求进行重试。例如:
func sendRequest(ctx context.Context, client *http.Client, url string, maxRetries int) (*http.Response, error) {
for i := 0; i < maxRetries; i++ {
subCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
req, err := http.NewRequestWithContext(subCtx, "GET", url, nil)
if err != nil {
cancel()
return nil, err
}
resp, err := client.Do(req)
cancel()
if err == nil {
return resp, nil
}
time.Sleep(time.Second)
}
return nil, fmt.Errorf("max retries reached")
}
优化建议和实践经验
- 集中管理
context
:可以在应用的入口处创建根 context
,然后在整个应用的调用链中传递,这样便于统一管理协程生命周期。
- 日志记录:在协程取消或超时发生时,记录详细的日志,便于排查问题。可以记录
context
的取消原因、超时时间等信息。
- 性能测试:通过性能测试工具,如Go语言的
benchmark
,测试不同 context
管理策略下系统的性能,从而找到最优方案。例如,测试不同工作池大小、不同协程启动策略下系统的吞吐量和响应时间。