ขั้น production: scheduler, GC, PGO, TLS, Docker
new vs make ได้ ใช้ pointer receiver ถูกerrors.Is/As + %w ได้go + chan + select + close + range + WaitGroup + Mutex + Context ได้โดยไม่ดูgo test -race ผ่านเช็กเครื่อง:
Go 1.26 (ก.พ. 2026):
new(ค่า) ใส่ค่าเริ่มต้นได้เลย: p := new(int64(300)) แทน 2 บรรทัดgo fix รื้อใหม่เป็น modernizers + มี //go:fix inlinecrypto/hpke, testing/cryptotest, ทดลอง simd/archsimd, runtime/secret, goroutineleak pprofgo.mod ใหม่ default เป็นเวอร์ชันก่อนหน้า (เช่นลง 1.26 ได้ go 1.25) เพื่อเข้ากับคนอื่นง่ายGo 1.27 (ส.ค. 2026):
func (r *Rand) N[T intType](n T) T ไม่ต้องเขียนแยกทุก typeGopher{Burrow: ...} ไม่ต้องห่อ Habitat{...}[T] ใน composite literal, conversion, ส่งเข้า channel: ch <- GenericFormattergo fix เพิ่ม atomictypes embedlit slicesbackward unsafefuncs, go doc pkg@version, go mod tidy รวม require ให้เองencoding/json/v2 + jsontext มาแล้ว + เอาไส้ v2 ยัดใน encoding/json เดิม = เดิมเร็วขึ้นโดยไม่แก้httptest.NewTestServer (in-memory ไม่ใช้เน็ตจริง) คู่กับ testing/synctesttlssecpmlkem=0, urlstrictcolons=0database/sql debug/elf encoding/json net/http os simdนึกว่าโรงอาหาร:
โค้ดเราไม่ต้องสั่ง 3 คนนี้โดยตรง แต่ต้องเขียนให้เขาทำงานง่าย:
// ดี: งานรอเน็ตให้ใช้ net/http ธรรมดา netpoller ช่วยเอง
resp, err := http.Get(url)
// ไม่ดี: เอา loop เปล่าๆ เผา CPU ทำให้ scheduler เหนื่อย
// for { } // อย่าทำ
// ดี: ถ้าต้องรอให้ใช้ channel / context
select {
case <-ch:
case <-ctx.Done():
}GODEBUG=gctrace=1 go run . # ดู GC ทำงาน (มี pause เท่าไหร่)
go tool pprof -http=:8080 cpu.out # 1.26 ขึ้นไปเปิดมาเป็น flame graph เลย ดูง่ายของทดลองใน 1.26 ที่สาย advanced ควรรู้ชื่อไว้:
runtime/secret — ลบขยะความลับ (เช่น key) ให้สะอาด เหมาะกับงาน cryptoruntime/pprof goroutineleak — โปรไฟล์ดูว่า goroutine ตัวไหนลืมปิดแล้วรั่ว (ดูคู่กับบท 6)Go เลือกให้เองว่าเอาไว้ไหน เรียกว่า escape analysis ดูได้:
Go 1.26 compiler เก่งขึ้น เอาที่เก็บ slice ไว้บน stack ได้บ่อยขึ้น = เร็วขึ้นฟรีๆ โดยไม่ต้องแก้โค้ด
//go:fix inline ให้ go fix ช่วยไล่ inline ให้simd/archsimd = สั่ง CPU ทำหลายเลขพร้อมกันในคำสั่งเดียว เหมาะกับงานภาพ เสียง AI ถ้าไม่ได้ทำสายนี้รู้ชื่อไว้พอ ยังเป็น experimental
| งาน | ใช้อะไร |
|---|---|
| แตะ map / struct ร่วมกันสั้นๆ | sync.Mutex |
| อ่านเยอะ เขียนน้อย | sync.RWMutex |
| รอหลายงานจบ | sync.WaitGroup |
| ทำครั้งเดียว | sync.Once |
| ใช้ซ้ำของแพง (buffer) | sync.Pool |
| นับเลข / flag ง่ายๆ | sync/atomic |
var mu sync.RWMutex
mu.RLock() // อ่านพร้อมกันได้หลายคน
v := m[k]
mu.RUnlock()
mu.Lock() // เขียนต้องเข้าเดี่ยว
m[k] = v2
mu.Unlock()
var n atomic.Int64
n.Add(1)
fmt.Println(n.Load())Go 1.26 มี modernizer atomictypes ถ้าโค้ดเก่าใช้ atomic.AddInt64(&x) มันจะแนะนำให้เปลี่ยนเป็น atomic.Int64 ที่อ่านง่ายกว่า รัน:
ตรวจเสมอ:
type Number interface {
~int | ~int64 | ~float64 // ~ = เอา type ที่ไส้เป็นแบบนี้ด้วย
}
func Sum[T Number](s []T) T {
var total T
for _, v := range s { total += v }
return total
}Generic อ้างตัวเองได้แล้ว ทำโครงสร้างแบบต้นไม้/กราฟง่ายขึ้น:
มือ advanced จำว่าเอาไว้ทำ interface ซับซ้อน ไม่ต้องใช้ any แล้ว cast มั่ว
เมื่อก่อน method ห้าม generic ตอนนี้ได้แล้ว:
และไม่ต้องเขียน [T] ย้ำในที่พวกนี้แล้ว compiler เดาให้:
func GenericFormatter[T any](v T) string { return fmt.Sprintf("value: %v", v) }
type IntFormatter func(int) string
formatters := []IntFormatter{GenericFormatter} // เดา T=int
fn := IntFormatter(GenericFormatter) // conversion ก็เดา
ch := make(chan IntFormatter, 1)
ch <- GenericFormatter // ส่งเข้า channel ก็เดา (เกี่ยวกับ Effective Go #channels ตรงๆ)ทริก: ถ้า inference ไม่ออกให้เขียนชัดๆ GenericFormatter[int] อย่าฝืนให้เดา
func gen(ctx context.Context, nums ...int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for _, n := range nums {
select {
case out <- n:
case <-ctx.Done():
return
}
}
}()
return out
}ทุก stage ต้องฟัง ctx.Done() ไม่งั้นยกเลิกแล้วค้าง
sem := make(chan struct{}, 10) // พร้อมกันได้ 10 งาน
for _, job := range jobs {
sem <- struct{}{}
wg.Add(1)
go func(j Job) {
defer wg.Done()
defer func() { <-sem }()
do(j)
}(job)
}ถ้าใช้ golang.org/x/sync/errgroup จะสั้นลง (ได้ context + เก็บ error แรก + รอให้):
g, ctx := errgroup.WithContext(ctx)
g.SetLimit(10)
for _, j := range jobs {
g.Go(func() error { return do(ctx, j) })
}
if err := g.Wait(); err != nil { ... }errgroupcontext.Background() ใช้แค่ขอบนสุด (main / request แรก)cancel มาแล้ว defer cancel() เสมอctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
req = req.WithContext(ctx) // ส่งต่อให้ http / db ด้วยเช็ก goroutine รั่ว:
runtime.NumGoroutine() พิมพ์ดูว่าเพิ่มไม่หยุดไหมruntime/pprof + ของทดลอง goroutineleak (1.26) + testing/synctest (1.27) ไว้เทสต์ว่าเลิกครบไหม// เทสต์ว่าไม่มีงานค้าง (ไอเดีย synctest)
synctest.Test(t, func(t *testing.T) {
// รันโค้ด concurrent ในโลกจำลองเวลา
})Go 1.27 เอาไส้ json/v2 มาใส่ใน encoding/json เดิมแล้ว แปลว่าโค้ดนี้เร็วขึ้นโดยไม่แก้:
อยากได้ของใหม่ค่อยขยับ:
encoding/json/v2 — ตั้งค่าได้ (เข้มเรื่อง unknown fields, case, เวลา)encoding/json/jsontext — สตรีมทีละ token เหมาะกับไฟล์ยักษ์ ไม่ต้องโหลดทั้งก้อนimport jsonv2 "encoding/json/v2"
// ตัวอย่างไอเดีย: ตั้ง option เข้มขึ้น
// jsonv2.Marshal(v, jsonv2.Deterministic(true))1.27 มี httptest.NewTestServer เป็น in-memory server คู่กับ synctest = เทสต์ handler concurrent ได้โดยไม่ flaky:
srv := httptest.NewTestServer(myHandler) // ไม่กินพอร์ตจริง
defer srv.Close()
resp, _ := http.Get(srv.URL)จำไว้ทำ handler ให้รับ ctx มาด้วย จะได้ยกเลิก + timeout ได้เหมือนบท 6
ไม่ต้องจำคณิตศาสตร์ จำว่าเมื่อไหร่ใช้:
crypto/hpke (ใหม่ 1.26): กล่องเข้ารหัสให้คนอื่นเปิดได้คนเดียว (HPKE RFC 9180) มี hybrid แบบกันควอนตัมด้วยcrypto/mlkem/mlkemtest + tls hybrid (1.26 เปิด default): TLS คุยกันแบบกันควอนตัมโดยอัตโนมัติ ถ้าเจอปัญหากับ server เก่าใช้ GODEBUG=tlssecpmlkem=0 ปิดชั่วคราวได้ (แต่ควรแก้ server มากกว่า)net/url เข้มขึ้น (1.26): URL แปลกๆ มี : ผิดที่มีโอกาสโดนปัดตก ถ้าต้องการแบบเก่าใช้ GODEBUG=urlstrictcolons=0 ชั่วคราวruntime/secret (ทดลอง): ล้าง key ใน memory ให้เกลี้ยง ลดโอกาสหลุดไปอยู่ใน dumpเช็กลิสต์ก่อนขึ้นโปรดักชัน:
crypto/x509 html/template net/url os)go vet + go fix ดูเรื่อง unsafefuncsgo test ./...
go test -race ./...
go test -run TestX -v
go test -fuzz=FuzzParse -fuzztime=30s # ปล่อยลิงพิมพ์มั่วหาบั๊ก
go test -bench=. -benchmem # วัดเร็ว+กินแรม
go tool pprof -http=:8080 cpu.out # 1.26 เปิดมาเป็น flame graph ดูง่ายว่าใครกินเวลาFuzz สั้นๆ:
func FuzzParse(f *testing.F) {
f.Add("1-2")
f.Fuzz(func(t *testing.T, s string) {
// โยนขยะเข้าไปแล้วต้องไม่ panic
_, _ = Parse(s)
})
}Benchmark + synctest:
func BenchmarkSq(b *testing.B) {
for i := 0; i < b.N; i++ { _ = sq(gen(1,2,3)) }
}
// synctest ไว้เทสต์โค้ด channel/time โดยไม่ต้อง sleep จริง ลด flakygo fix ./... # แก้โค้ดเก่าเป็นแบบใหม่ (atomictypes embedlit slicesbackward unsafefuncs)
go vet ./... # ตรวจก่อนส่ง
go mod tidy # 1.27 รวม require ให้เหลือ 2 ก้อน direct/indirect เอง
go doc net/http@go1.27 # ดูของเวอร์ชันนั้น (ใหม่ 1.27: pkg@version)GODEBUG ที่เจอใน 1.26-1.27 (จำไว้เผื่อต้องปิดของใหม่ชั่วคราวตอนไล่บั๊ก):
GODEBUG=tlssecpmlkem=0 # ปิด hybrid post-quantum TLS กลับแบบเก่า
GODEBUG=urlstrictcolons=0 # กลับไป parse URL แบบหยวนๆ
GOEXPERIMENT=norandomizedheapbase64 # ปิดสุ่ม heap (สาย cgo เท่านั้น)วิธีไล่บั๊ก production:
GODEBUG=gctrace=1 ดู GC pausepprof flame graph ดูว่าใครกิน CPU/RAM-gcflags='-m' ดู escape-race หาแย่งกันenv > flag > file อย่า hardcode รหัสในโค้ด
srv := &http.Server{Addr: ":"+port, Handler: mux}
go srv.ListenAndServe()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit // รอสัญญาณตาย
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
srv.Shutdown(ctx) // เลิกแบบรอให้ request ค้างจบก่อนFROM golang:1.27 AS build
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o app .
FROM gcr.io/distroless/base
COPY --from=build /app/app /app
CMD ["/app"]CGO_ENABLED=0 = ไฟล์เดียวจบ ไม่ต้องแบก lib C (ตั้งแต่ 1.26 cgo เร็วขึ้น 30% แต่ถ้าไม่ใช้ C ปิดไปเลยเล็กสุด)
โจทย์: รับออเดอร์ → ตรวจของ → คิดเงิน → ส่งของ ทุกขั้นยกเลิกได้ + เทสต์ได้ + ปิดสวย
โครง:
ordersvc/
go.mod (go 1.27)
main.go (wiring + graceful)
order/
service.go (pipeline + errgroup + context)
service_test.go (table + synctest + fuzz)
api/
http.go (handler + NewTestServer test)แกน service (ย่อ):
func Process(ctx context.Context, ids []string) ([]int, error) {
g, ctx := errgroup.WithContext(ctx)
g.SetLimit(10)
out := make(chan int, len(ids))
for _, id := range ids {
g.Go(func() error {
price, err := Charge(ctx, id) // ข้างใน select ctx.Done()
if err != nil { return err }
select {
case out <- price:
return nil
case <-ctx.Done():
return ctx.Err()
}
})
}
go func() { g.Wait(); close(out) }() // ใช้จริงควรเก็บ error จาก Wait ด้วย errgroup
var total []int
for v := range out { total = append(total, v) }
return total, g.Wait()
}เช็กลิสต์ส่งงาน:
go fmt / vet / fix ผ่านgo test -race ผ่าน + มี fuzz 10 วิpprof ดูแล้วไม่มีคอขวดแปลกgo mod tidynet/url ใหม่ได้| คำ | แปลบ้านๆ |
|---|---|
| scheduler | ผู้จัดการแบ่งงานให้เตา |
| netpoller | พนักงานรับโทรศัพท์รอเน็ตโดยไม่กินเตา |
| Green Tea GC | แม่บ้านรุ่นใหม่ (1.26) เก็บไว หยุดสั้น |
| escape | ของหนีจากลิ้นชักไปโกดัง (stack→heap) |
| inline/PGO | ก็อปวางฟังก์ชันเล็ก / เอาทางคนเดินเยอะมาทำให้เร็ว |
| recursive constraint | generic ที่อ้างตัวเองได้ (1.26) |
| generic method | เมธอดครอบจักรวาลได้แล้ว (1.27) |
| errgroup | ทีมงานมีหัวหน้า แจกงาน+เก็บ error+ยกเลิกพร้อมกัน |
| backpressure | เบรกไม่ให้ producer เทของท่วม consumer |
| synctest | ห้องจำลองเวลาสำหรับเทสต์ concurrent (1.27) |
| json/v2 jsontext | เครื่อง JSON รุ่นใหม่ เร็ว+ตั้งค่าได้ |
| HPKE ML-KEM | กล่องเข้ารหัส + กุญแจกันควอนตัม (1.26) |
| flame graph | แผนที่ไฟ ดูว่าใครกิน CPU เยอะสุด |
| graceful shutdown | ตายสวย รอแขกเก่ากลับก่อนค่อยปิดร้าน |
เล่ม Advanced ฉบับภาษาง่าย อัปเดตสำหรับ Go 1.27.1 ตุลาคม 2026 — ถ้าอยากทวนพื้น channel/context กลับไปเปิดเล่ม Beginner ตอน 13 และเล่ม Intermediate ตอน 8-9 ได้เลย