go
package main
import (
"context"
"fmt"
"net/http"
"time"
)
// --- 1. time 包深度解析 ---
func timeDeepDive() {
fmt.Println("=== 1. time 包:除了计时,还能控制节奏 ===")
// A. 定时器 (Timer):只响一次的闹钟
timer := time.NewTimer(2 * time.Second)
fmt.Println("定时器已启动,等待 2 秒...")
<-timer.C // 阻塞,直到时间到
fmt.Println("闹钟响了!")
// B. 打点器 (Ticker):有节奏的脉搏
fmt.Println("\n打点器演示:每 500ms 跳动一次,跳动 3 次结束")
ticker := time.NewTicker(500 * time.Millisecond)
count := 0
for t := range ticker.C {
fmt.Printf("Tick at %v\n", t.Format("15:04:05.000"))
count++
if count >= 3 {
ticker.Stop() // 必须手动停止,否则会持续占用内存
break
}
}
// C. 超时控制 (Timeout): select 的经典用法
fmt.Println("\n超时控制:模拟一个耗时 3s 的任务,但我们只给 1s")
eventCh := make(chan string)
go func() {
time.Sleep(3 * time.Second)
eventCh <- "任务完成"
}()
select {
case res := <-eventCh:
fmt.Println("收到结果:", res)
case <-time.After(1 * time.Second): // 核心:这就是"超时炸弹"
fmt.Println("⚠️ 超时了!我不等了。")
}
}
// --- 2. net/http 包深度解析 ---
func httpDeepDive() {
fmt.Println("\n=== 2. net/http 包:工业级 HTTP 栈 ===")
// A. 深度定制 Client:不要直接用 http.Get
// 在生产环境中,这是必须的,用来控制资源
client := &http.Client{
Timeout: 5 * time.Second, // 整个请求(含握手、读数据)的总超时
Transport: &http.Transport{
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
DisableCompression: true,
},
}
// B. 使用 Context 控制请求
// 这在大型微服务中非常重要,父级取消,子级全断
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", "https://go.dev", nil)
fmt.Println("发起带 Context 的 HTTP 请求...")
resp, err := client.Do(req)
if err != nil {
fmt.Printf("请求失败(可能是超时): %v\n", err)
return
}
defer resp.Body.Close()
// C. 流式读取 (Streaming)
// Body 是一个流,不是内存中的字符串。读多少,占多少内存
fmt.Printf("状态码: %d\n", resp.StatusCode)
// 读取前 100 字节看看
buffer := make([]byte, 100)
n, _ := resp.Body.Read(buffer)
fmt.Printf("响应前 %d 字节内容: %s...\n", n, string(buffer[:n]))
}
func main() {
timeDeepDive()
httpDeepDive()
}
1. time 包:不仅看表,更能"控制节奏"
lib_deep_dive.go 里的 timeDeepDive() 函数看到这三种用法:
A. time.NewTimer (定时器)
就像一个"只响一次的闹钟"。设置好时间后,程序会通过 <-timer.C 在那里死等着,直到时间到了才往下走。这就像你煮泡面设定的 3 分钟倒计时。
B. time.NewTicker (打点器)
一旦开启,它就会跳动,每隔设定的时间,就往通道里塞一个信号。
实战场景:我们可以用它来做后台任务,规定"每隔 5 秒钟去数据库检查一次有没有新订单"。
注意:用完一定要 ticker.Stop(),否则节拍器会一直跳,造成内存泄漏。
C. time.After (超时炸弹) 🌟
最常用,我们在代码里模拟了一个耗时 3 秒的任务,但通过 select 和 time.After(1 * time.Second) 设下了一个陷阱。 谁先完成,就执行谁的代码块。完美解决了"如果你不理我,我也不能干等你一辈子"的问题。
2. net/http 包:工业级的网络栈
在 httpDeepDive() 函数里,我展示了真正在公司里写底层代码的姿势:
A. 深度定制 http.Client
绝对不要在生产环境直接用简单的 http.Get。我们创建了一个定制的 Client,里面规定了:
总超时 (Timeout):如果 5 秒还没处理完,直接砍掉。
连接池 (Transport):它能维持与目标服务器的长连接,而不是每次请求都重新握手,极大地提升了并发性能。
B. Context 的运用 Context(上下文)
是 Go 语言非常强大的一个包,通常和网路请求绑在一起。它就像一根"风筝线",当你发出一个复杂的请求(比如这个请求还要去调其他三个数据库),一旦前端用户点了一下"取消",或者触发了 Context 设置的超时,这根线一断,所有的下游请求会瞬间全部被撤回。
C. 流式读取响应 (resp.Body.Read)
在咱们之前的下载器里,我用 io.Copy 一次性抽干了数据。如果我要下一个 10GB 的文件怎么办? 答案就是"每次用一个小勺子(比如 100 字节的 buffer)舀水"。这样无论文件多大,你的程序永远只占几十 KB 的内存。