เรียน Go ภาษาง่าย 📘

🚀

หนังสือภาษา Go ระดับ Advanced ฉบับภาษาง่ายๆ

ขั้น production: scheduler, GC, PGO, TLS, Docker

15 บท
26 ข้อสอบ
0. เช็กก่อนขึ้น Advanced + ภาพรวม Go 1.26-1.27
ต้องได้แล้วก่อนอ่านเล่มนี้
  • แยก new vs make ได้ ใช้ pointer receiver ถูก
  • เขียน interface เล็กๆ + errors.Is/As + %w ได้
  • เขียน go + chan + select + close + range + WaitGroup + Mutex + Context ได้โดยไม่ดู
  • รัน go test -race ผ่าน

เช็กเครื่อง:

bash
go version
go env GOVERSION GOROOT GOPATH GOCACHE
สรุปของใหม่ที่เล่มนี้ใช้ (แบบสั้นๆ จำก่อน เจอของจริงทีหลัง)

Go 1.26 (ก.พ. 2026):

  1. new(ค่า) ใส่ค่าเริ่มต้นได้เลย: p := new(int64(300)) แทน 2 บรรทัด
  2. Generic type อ้างตัวเองใน constraint ได้ (recursive constraint) ทำ data structure ซับซ้อนง่ายขึ้น
  3. Green Tea GC เปิดเป็นค่าเริ่มต้น — หยุดน้อยลง
  4. cgo ช้าลดลง ~30%
  5. compiler เอา slice ไปไว้บน stack ได้บ่อยขึ้น
  6. go fix รื้อใหม่เป็น modernizers + มี //go:fix inline
  7. ของใหม่: crypto/hpke, testing/cryptotest, ทดลอง simd/archsimd, runtime/secret, goroutineleak pprof
  8. go.mod ใหม่ default เป็นเวอร์ชันก่อนหน้า (เช่นลง 1.26 ได้ go 1.25) เพื่อเข้ากับคนอื่นง่าย

Go 1.27 (ส.ค. 2026):

  1. Generic methods ได้แล้ว: func (r *Rand) N[T intType](n T) T ไม่ต้องเขียนแยกทุก type
  2. Struct literal key เป็น field ข้างในได้เลย: Gopher{Burrow: ...} ไม่ต้องห่อ Habitat{...}
  3. Type inference ทุกที่: generic func ไม่ต้องใส่ [T] ใน composite literal, conversion, ส่งเข้า channel: ch <- GenericFormatter
  4. go fix เพิ่ม atomictypes embedlit slicesbackward unsafefuncs, go doc pkg@version, go mod tidy รวม require ให้เอง
  5. encoding/json/v2 + jsontext มาแล้ว + เอาไส้ v2 ยัดใน encoding/json เดิม = เดิมเร็วขึ้นโดยไม่แก้
  6. httptest.NewTestServer (in-memory ไม่ใช้เน็ตจริง) คู่กับ testing/synctest
  7. บังคับ macOS 13+ , มี GODEBUG ใหม่ tlssecpmlkem=0, urlstrictcolons=0
  8. 1.27.1 (ก.ย. 2026) แก้ cgo compiler runtime database/sql debug/elf encoding/json net/http os simd

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


1. Runtime ลึก: Scheduler GC Netpoller
ภาพจำ 3 พระเอก

นึกว่าโรงอาหาร:

  • Scheduler (M:P:G) = ผู้จัดการ แบ่งพ่อครัว (goroutine = G) ไปเตา (P) ที่ผูกกับคนงานจริง (M = thread)
  • Netpoller = พนักงานรับโทรศัพท์ รอเน็ตโดยไม่กินเตา งานเน็ตเยอะๆ เลยไม่เปลือง thread
  • GC (Green Tea) = แม่บ้านเก็บจาน ตั้งแต่ 1.26 เป็นแม่บ้านรุ่นใหม่ เก็บไว หยุดเสิร์ฟสั้นลง

โค้ดเราไม่ต้องสั่ง 3 คนนี้โดยตรง แต่ต้องเขียนให้เขาทำงานง่าย:

go
// ดี: งานรอเน็ตให้ใช้ net/http ธรรมดา netpoller ช่วยเอง
resp, err := http.Get(url)

// ไม่ดี: เอา loop เปล่าๆ เผา CPU ทำให้ scheduler เหนื่อย
// for { } // อย่าทำ
// ดี: ถ้าต้องรอให้ใช้ channel / context
select {
case <-ch:
case <-ctx.Done():
}
ดูว่าใครกินอะไร
bash
GODEBUG=gctrace=1 go run .  # ดู GC ทำงาน (มี pause เท่าไหร่)
go tool pprof -http=:8080 cpu.out  # 1.26 ขึ้นไปเปิดมาเป็น flame graph เลย ดูง่าย

ของทดลองใน 1.26 ที่สาย advanced ควรรู้ชื่อไว้:

  • runtime/secret — ลบขยะความลับ (เช่น key) ให้สะอาด เหมาะกับงาน crypto
  • runtime/pprof goroutineleak — โปรไฟล์ดูว่า goroutine ตัวไหนลืมปิดแล้วรั่ว (ดูคู่กับบท 6)

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


2. Memory: Stack Heap Escape PGO Inline
Stack vs Heap แบบ 1 ประโยค
  • Stack = ลิ้นชักประจำตัว เร็ว เปิด-ปิดเอง
  • Heap = โกดังกลาง ช้า ต้องให้ GC เก็บ

Go เลือกให้เองว่าเอาไว้ไหน เรียกว่า escape analysis ดูได้:

bash
go build -gcflags='-m' ./...  # ดูว่าตัวไหนหนีไป heap (escapes to heap)

Go 1.26 compiler เก่งขึ้น เอาที่เก็บ slice ไว้บน stack ได้บ่อยขึ้น = เร็วขึ้นฟรีๆ โดยไม่ต้องแก้โค้ด

Inline + PGO คืออะไร?
  • Inline = แทนที่จะโทรเรียกฟังก์ชันเล็กๆ compiler ก็อปวางมาเลย เร็วกว่า 1.26 มี //go:fix inline ให้ go fix ช่วยไล่ inline ให้
  • PGO (Profile-Guided Optimization) = เอาผลวิ่งจริงมาบอก compiler ว่าทางไหนคนเดินเยอะ ให้优化ทางนั้น
bash
go build -pgo=cpu.pprof -o app .
SIMD (ของทดลอง 1.26)

simd/archsimd = สั่ง CPU ทำหลายเลขพร้อมกันในคำสั่งเดียว เหมาะกับงานภาพ เสียง AI ถ้าไม่ได้ทำสายนี้รู้ชื่อไว้พอ ยังเป็น experimental


✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


3. Sync + Atomics + Memory Model แบบใช้งานจริง
เลือกอาวุธให้ถูก
งานใช้อะไร
แตะ map / struct ร่วมกันสั้นๆsync.Mutex
อ่านเยอะ เขียนน้อยsync.RWMutex
รอหลายงานจบsync.WaitGroup
ทำครั้งเดียวsync.Once
ใช้ซ้ำของแพง (buffer)sync.Pool
นับเลข / flag ง่ายๆsync/atomic
go
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 ที่อ่านง่ายกว่า รัน:

bash
go fix ./...
Memory Model จำ 1 ข้อพอ

ตรวจเสมอ:

bash
go test -race ./...
go run -race .

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


4. Generics ขั้นสุด: Constraints Recursive Generic Methods 1.27
Constraints = บอกว่าว่าที่ T ทำอะไรได้บ้าง
go
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
}
Recursive constraint (ใหม่ 1.26)

Generic อ้างตัวเองได้แล้ว ทำโครงสร้างแบบต้นไม้/กราฟง่ายขึ้น:

go
// ไอเดีย: T ต้องเป็นอะไรที่เทียบกับตัวเองได้
type Node[T Node[T]] interface {
    Next() T
}

มือ advanced จำว่าเอาไว้ทำ interface ซับซ้อน ไม่ต้องใช้ any แล้ว cast มั่ว

Generic methods (ใหม่ 1.27) + inference ทุกที่

เมื่อก่อน method ห้าม generic ตอนนี้ได้แล้ว:

go
// จาก math/rand/v2: เมธอดเดียวจบทุก int
func (r *Rand) N[Int intType](n Int) Int

และไม่ต้องเขียน [T] ย้ำในที่พวกนี้แล้ว compiler เดาให้:

go
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] อย่าฝืนให้เดา


✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


5. Concurrency Patterns ขั้น production
Pipeline + Cancel
go
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() ไม่งั้นยกเลิกแล้วค้าง

Fan-out / Fan-in + จำกัดความพร้อมกัน (semaphore)
go
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 แรก + รอให้):

go
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 { ... }
Backpressure
  • producer เร็วกว่า consumer ให้ใช้ buffered + select + drop หรือ block อย่างตั้งใจ อย่าปล่อยให้ channel โตไม่จำกัด (กินแรมตาย)
  • งานที่ห้ามหายให้ส่ง error กลับผ่านอีก channel หรือ errgroup

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


6. Context ขั้นลึก: ยกเลิก ดักรั่ว ส่งค่า
กฎ 3 ข้อ
  1. context.Background() ใช้แค่ขอบนสุด (main / request แรก)
  2. ได้ cancel มาแล้ว defer cancel() เสมอ
  3. อย่ายัดของใหญ่ใน value เอาไว้แค่ request-id / user-id
go
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) ไว้เทสต์ว่าเลิกครบไหม
go
// เทสต์ว่าไม่มีงานค้าง (ไอเดีย synctest)
synctest.Test(t, func(t *testing.T) {
    // รันโค้ด concurrent ในโลกจำลองเวลา
})

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


7. HTTP + JSON รุ่นใหม่ 1.27: v2 jsontext NewTestServer
JSON: เขียนเดิม เร็วขึ้นเอง + อยากซิ่งค่อยใช้ v2

Go 1.27 เอาไส้ json/v2 มาใส่ใน encoding/json เดิมแล้ว แปลว่าโค้ดนี้เร็วขึ้นโดยไม่แก้:

go
b, err := json.Marshal(user)
json.Unmarshal(b, &user)

อยากได้ของใหม่ค่อยขยับ:

  • encoding/json/v2 — ตั้งค่าได้ (เข้มเรื่อง unknown fields, case, เวลา)
  • encoding/json/jsontext — สตรีมทีละ token เหมาะกับไฟล์ยักษ์ ไม่ต้องโหลดทั้งก้อน
go
import jsonv2 "encoding/json/v2"
// ตัวอย่างไอเดีย: ตั้ง option เข้มขึ้น
// jsonv2.Marshal(v, jsonv2.Deterministic(true))
HTTP: เทสต์โดยไม่ใช้เน็ตจริง

1.27 มี httptest.NewTestServer เป็น in-memory server คู่กับ synctest = เทสต์ handler concurrent ได้โดยไม่ flaky:

go
srv := httptest.NewTestServer(myHandler) // ไม่กินพอร์ตจริง
defer srv.Close()
resp, _ := http.Get(srv.URL)

จำไว้ทำ handler ให้รับ ctx มาด้วย จะได้ยกเลิก + timeout ได้เหมือนบท 6


✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


8. Security + Crypto ใหม่: HPKE ML-KEM TLS Hybrid

ไม่ต้องจำคณิตศาสตร์ จำว่าเมื่อไหร่ใช้:

  • 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

เช็กลิสต์ก่อนขึ้นโปรดักชัน:

  • อัป Go เป็น 1.27.1 (มีแพตช์ crypto/x509 html/template net/url os)
  • รัน go vet + go fix ดูเรื่อง unsafefuncs
  • อย่า log secret / token เต็มๆ

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


9. Testing ขั้นเทพ: Fuzz Bench Synctest Race Pprof
bash
go 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 สั้นๆ:

go
func FuzzParse(f *testing.F) {
    f.Add("1-2")
    f.Fuzz(func(t *testing.T, s string) {
        // โยนขยะเข้าไปแล้วต้องไม่ panic
        _, _ = Parse(s)
    })
}

Benchmark + synctest:

go
func BenchmarkSq(b *testing.B) {
    for i := 0; i < b.N; i++ { _ = sq(gen(1,2,3)) }
}
// synctest ไว้เทสต์โค้ด channel/time โดยไม่ต้อง sleep จริง ลด flaky

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


10. Toolchain โหด: go fix modernizers go vet GODEBUG
bash
go 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 (จำไว้เผื่อต้องปิดของใหม่ชั่วคราวตอนไล่บั๊ก):

code
GODEBUG=tlssecpmlkem=0   # ปิด hybrid post-quantum TLS กลับแบบเก่า
GODEBUG=urlstrictcolons=0 # กลับไป parse URL แบบหยวนๆ
GOEXPERIMENT=norandomizedheapbase64 # ปิดสุ่ม heap (สาย cgo เท่านั้น)

วิธีไล่บั๊ก production:

  1. GODEBUG=gctrace=1 ดู GC pause
  2. pprof flame graph ดูว่าใครกิน CPU/RAM
  3. -gcflags='-m' ดู escape
  4. -race หาแย่งกัน

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


11. ทำโปรดักชัน: Config Log Graceful Docker
Config จำ 3 ที่

env > flag > file อย่า hardcode รหัสในโค้ด

go
port := os.Getenv("PORT")
if port == "" { port = "8080" }
Log ด้วย slog (มากับ std)
go
import "log/slog"
slog.Info("start", "port", port)
slog.Error("db fail", "err", err)
Graceful shutdown (ตายสวย)
go
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 ค้างจบก่อน
Dockerfile เล็กๆ
dockerfile
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 ปิดไปเลยเล็กสุด)


✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


12. Mini Project: Order Service พร้อมเสิร์ฟ

โจทย์: รับออเดอร์ → ตรวจของ → คิดเงิน → ส่งของ ทุกขั้นยกเลิกได้ + เทสต์ได้ + ปิดสวย

โครง:

code
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 (ย่อ):

go
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 ดูแล้วไม่มีคอขวดแปลก
  • อัป 1.27.1 + go mod tidy
  • มี graceful shutdown + timeout ทุก call นอก
  • ไม่ log secret, URL แปลกผ่าน net/url ใหม่ได้

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


คำศัพท์ท้ายเล่ม
คำแปลบ้านๆ
schedulerผู้จัดการแบ่งงานให้เตา
netpollerพนักงานรับโทรศัพท์รอเน็ตโดยไม่กินเตา
Green Tea GCแม่บ้านรุ่นใหม่ (1.26) เก็บไว หยุดสั้น
escapeของหนีจากลิ้นชักไปโกดัง (stack→heap)
inline/PGOก็อปวางฟังก์ชันเล็ก / เอาทางคนเดินเยอะมาทำให้เร็ว
recursive constraintgeneric ที่อ้างตัวเองได้ (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 ได้เลย