# 25 คำถามสัมภาษณ์ Go ยอดนิยม: คู่มือฉบับสมบูรณ์สำหรับนักพัฒนา > พิชิตการสัมภาษณ์ Go ด้วย 25 คำถามที่ถูกถามบ่อย goroutine, channel, interface และรูปแบบการทำงานพร้อมกันพร้อมตัวอย่างโค้ด - Published: 2026-02-06 - Updated: 2026-04-28 - Author: SharpSkill - Tags: go, golang, interview, concurrency, goroutines - Reading time: 18 min --- การสัมภาษณ์เชิงเทคนิคของ Go ประเมินความเข้าใจในแนวคิดหลักของภาษา ทั้งเรื่องการทำงานพร้อมกัน การจัดการหน่วยความจำ และรูปแบบเชิงสำนวน คู่มือนี้รวบรวม 25 คำถามที่พบบ่อยที่สุดพร้อมคำตอบโดยละเอียดและตัวอย่างโค้ด > **คำแนะนำในการสัมภาษณ์** > > Go ให้ความสำคัญกับความเรียบง่ายและการอ่านง่าย ผู้สัมภาษณ์มักชื่นชอบคำตอบกระชับที่แสดงความเข้าใจเชิงลึกมากกว่าวิธีแก้ที่ซับซ้อนเกินจำเป็น ## พื้นฐานของภาษา Go ### 1. `var` ต่างจาก `:=` อย่างไร การประกาศ `var` ระบุชนิดข้อมูลได้ชัดเจนและใช้ได้ที่ระดับ package ตัวดำเนินการ `:=` อนุมานชนิดอัตโนมัติแต่ใช้ได้เฉพาะภายในฟังก์ชัน ```go // declaration.go package main // Package level - var required var globalConfig = "production" func main() { // var with explicit type var count int = 10 // var with type inference var name = "Alice" // Short declaration - functions only age := 25 // Multiple declarations var ( host = "localhost" port = 8080 ) } ``` ภายในฟังก์ชันมักนิยมใช้รูปแบบสั้น `:=` เพราะกระชับ ส่วน `var` ยังจำเป็นเมื่อประกาศตัวแปรระดับ package ### 2. ระบบชนิดข้อมูลของ Go ทำงานอย่างไร Go ใช้การกำหนดชนิดแบบสแตติกพร้อมการอนุมานชนิด ภาษาให้ความแตกต่างระหว่างชนิดแบบค่า (คัดลอกเมื่อกำหนดค่า) กับชนิดแบบอ้างอิง (ใช้โครงสร้างพื้นฐานร่วมกัน) ```go // types.go package main import "fmt" func main() { // Value types - full copy a := [3]int{1, 2, 3} b := a // Copies the array b[0] = 100 // Doesn't modify a fmt.Println(a) // [1 2 3] // Reference types - share data slice1 := []int{1, 2, 3} slice2 := slice1 // Same underlying array slice2[0] = 100 // Also modifies slice1 fmt.Println(slice1) // [100 2 3] // Maps are also references m1 := map[string]int{"a": 1} m2 := m1 m2["a"] = 100 fmt.Println(m1["a"]) // 100 } ``` อาเรย์เป็นชนิดแบบค่า ขณะที่ slice, map และ channel เป็นชนิดแบบอ้างอิง ### 3. อาเรย์กับ slice แตกต่างกันอย่างไร อาเรย์มีขนาดคงที่ที่กำหนดในเวลาคอมไพล์ ส่วน slice เป็นมุมมองแบบไดนามิกบนอาเรย์ที่อยู่เบื้องหลังและประกอบด้วยสามส่วน ได้แก่ พอยน์เตอร์ ความยาว และความจุ ```go // arrays_slices.go package main import "fmt" func main() { // Array - fixed size, value type arr := [5]int{1, 2, 3, 4, 5} // Slice - view over the array slice := arr[1:4] // [2 3 4] fmt.Printf("len=%d, cap=%d\n", len(slice), cap(slice)) // len=3, cap=4 // Modifications affect original array slice[0] = 20 fmt.Println(arr) // [1 20 3 4 5] // Direct creation with make dynamic := make([]int, 3, 10) // len=3, cap=10 // Append may reallocate dynamic = append(dynamic, 1, 2, 3, 4, 5) } ``` ใน Go slice เป็นชนิดที่นิยมใช้ที่สุดสำหรับคอลเลกชันแบบไดนามิก ### 4. คำสั่ง `defer` ทำงานอย่างไร `defer` กำหนดตารางให้เรียกใช้ฟังก์ชันเมื่อสิ้นสุดฟังก์ชันที่ครอบอยู่ การเรียกที่ถูกเลื่อนจะถูกซ้อนไว้และทำงานตามลำดับ LIFO (เข้าสุดท้าย ออกก่อน) ```go // defer.go package main import ( "fmt" "os" ) func main() { // LIFO order defer fmt.Println("1") defer fmt.Println("2") defer fmt.Println("3") // Prints: 3, 2, 1 } // Typical use case: resource cleanup func readFile(path string) ([]byte, error) { file, err := os.Open(path) if err != nil { return nil, err } defer file.Close() // Always executes // Read file... return os.ReadFile(path) } // Caution: arguments are evaluated immediately func deferArgs() { x := 10 defer fmt.Println(x) // Captures 10 x = 20 // Prints: 10 } ``` `defer` รับประกันการทำงานแม้ขณะเกิด panic จึงเหมาะกับการคืนทรัพยากร ### 5. interface ใน Go คืออะไร interface กำหนดชุดของเมธอด ชนิดใดที่สร้างเมธอดเหล่านั้นจะเข้ากับ interface โดยอัตโนมัติโดยไม่ต้องประกาศชัดเจน ```go // interfaces.go package main import "fmt" // Interface definition type Writer interface { Write([]byte) (int, error) } // Type that implicitly implements Writer type FileLogger struct { path string } func (f *FileLogger) Write(data []byte) (int, error) { // Write to file fmt.Println("Writing to", f.path) return len(data), nil } // Empty interface - accepts any type func printAny(v interface{}) { fmt.Printf("Type: %T, Value: %v\n", v, v) } // Type assertion func process(w Writer) { // Type check if fl, ok := w.(*FileLogger); ok { fmt.Println("FileLogger with path:", fl.path) } } ``` การสร้าง interface แบบนัยช่วยให้ package ต่าง ๆ ถูกแยกออกจากกันได้อย่างชัดเจน ## การทำงานพร้อมกันและ goroutine ### 6. goroutine คืออะไร และต่างจาก thread อย่างไร goroutine คือ thread น้ำหนักเบาที่บริหารโดย runtime ของ Go ใช้สแตกเพียงไม่กี่ KB (เทียบกับหลาย MB ของ thread ระบบ) และตัวจัดตารางเวลาของ Go จะสลับการทำงานของ goroutine นับพันไปยัง thread ระบบเพียงไม่กี่ตัว ```go // goroutines.go package main import ( "fmt" "sync" "time" ) func main() { var wg sync.WaitGroup // Launch 1000 goroutines for i := 0; i < 1000; i++ { wg.Add(1) go func(id int) { defer wg.Done() time.Sleep(100 * time.Millisecond) fmt.Printf("Goroutine %d finished\n", id) }(i) // Pass i by value } wg.Wait() fmt.Println("All goroutines completed") } ``` > **ข้อผิดพลาดที่พบบ่อย** > > ตัวแปรของลูปควรส่งให้ goroutine แบบส่งค่าเสมอ มิฉะนั้น goroutine ทุกตัวอาจจับค่าสุดท้ายเหมือนกันทั้งหมด ### 7. channel ทำงานอย่างไร channel ทำให้ goroutine สื่อสารและซิงโครไนซ์กันได้ มีทั้งแบบมีบัฟเฟอร์ (มีความจุ) และแบบไม่มีบัฟเฟอร์ (ซิงโครนัส) ```go // channels.go package main import "fmt" func main() { // Unbuffered channel - blocks until received ch := make(chan int) go func() { ch <- 42 // Blocks until read }() value := <-ch // Receives value fmt.Println(value) // Buffered channel - doesn't block until full buffered := make(chan string, 2) buffered <- "first" buffered <- "second" // buffered <- "third" // Would block fmt.Println(<-buffered) // "first" fmt.Println(<-buffered) // "second" } ``` channel แบบไม่มีบัฟเฟอร์รับประกันการซิงโครไนซ์ ส่วน channel ที่มีบัฟเฟอร์ช่วยปลดล็อกเรื่องเวลาให้กับผู้ส่งและผู้รับ ### 8. ใช้ `select` กับหลาย channel อย่างไร `select` รอการดำเนินการบน channel หลายตัวพร้อมกัน การดำเนินการที่พร้อมก่อนจะถูกทำงาน หากมีหลายตัวพร้อมพร้อมกันจะถูกสุ่มเลือก ```go // select.go package main import ( "fmt" "time" ) func main() { ch1 := make(chan string) ch2 := make(chan string) go func() { time.Sleep(100 * time.Millisecond) ch1 <- "from ch1" }() go func() { time.Sleep(200 * time.Millisecond) ch2 <- "from ch2" }() // Wait with timeout for i := 0; i < 2; i++ { select { case msg := <-ch1: fmt.Println(msg) case msg := <-ch2: fmt.Println(msg) case <-time.After(500 * time.Millisecond): fmt.Println("Timeout") } } // Non-blocking select with default select { case msg := <-ch1: fmt.Println(msg) default: fmt.Println("No message available") } } ``` `select` คือเครื่องมือพื้นฐานสำหรับจัดการการทำงานพร้อมกันใน Go อย่างสง่างาม ### 9. ป้องกัน race condition อย่างไร race condition เกิดเมื่อ goroutine หลายตัวเข้าถึงข้อมูลร่วมกันโดยไม่มีการซิงโครไนซ์ Go จัดเตรียมกลไกป้องกันไว้หลายแบบ ```go // race_conditions.go package main import ( "fmt" "sync" "sync/atomic" ) // Solution 1: Mutex type SafeCounter struct { mu sync.Mutex count int } func (c *SafeCounter) Increment() { c.mu.Lock() defer c.mu.Unlock() c.count++ } // Solution 2: RWMutex for read-heavy workloads type Cache struct { mu sync.RWMutex data map[string]string } func (c *Cache) Get(key string) string { c.mu.RLock() // Multiple readers allowed defer c.mu.RUnlock() return c.data[key] } func (c *Cache) Set(key, value string) { c.mu.Lock() // Single writer defer c.mu.Unlock() c.data[key] = value } // Solution 3: atomic for simple counters var atomicCounter int64 func incrementAtomic() { atomic.AddInt64(&atomicCounter, 1) } func main() { // Detection: go run -race main.go counter := SafeCounter{} var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go func() { defer wg.Done() counter.Increment() }() } wg.Wait() fmt.Println("Count:", counter.count) } ``` แฟล็กของคอมไพเลอร์ `-race` ช่วยตรวจจับ race condition ระหว่างการทำงานจริง ### 10. อธิบายรูปแบบ worker pool รูปแบบ worker pool จำกัดการทำงานพร้อมกันโดยสร้าง goroutine จำนวนคงที่เพื่อประมวลผลงานจากคิว ```go // worker_pool.go package main import ( "fmt" "sync" "time" ) func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) { defer wg.Done() for job := range jobs { fmt.Printf("Worker %d processing job %d\n", id, job) time.Sleep(100 * time.Millisecond) // Simulate work results <- job * 2 } } func main() { const numJobs = 10 const numWorkers = 3 jobs := make(chan int, numJobs) results := make(chan int, numJobs) var wg sync.WaitGroup // Start workers for w := 1; w <= numWorkers; w++ { wg.Add(1) go worker(w, jobs, results, &wg) } // Send jobs for j := 1; j <= numJobs; j++ { jobs <- j } close(jobs) // Wait and close results go func() { wg.Wait() close(results) }() // Collect results for result := range results { fmt.Println("Result:", result) } } ``` รูปแบบนี้ช่วยลดภาระหน่วยความจำและ CPU ที่อาจเกิดจากการสร้าง goroutine จำนวนมากเกินไป ## การจัดการข้อผิดพลาดและ panic/recover ### 11. จัดการ error ใน Go อย่างไร Go ใช้ค่ากลับแบบชัดเจนสำหรับ error โดยไม่มี exception ตามธรรมเนียม error เป็นพารามิเตอร์ลำดับสุดท้ายที่ถูกส่งกลับ ```go // errors.go package main import ( "errors" "fmt" ) // Sentinel errors for comparison var ( ErrNotFound = errors.New("resource not found") ErrUnauthorized = errors.New("access unauthorized") ) // Custom error type type ValidationError struct { Field string Message string } func (e *ValidationError) Error() string { return fmt.Sprintf("validation %s: %s", e.Field, e.Message) } func validateAge(age int) error { if age < 0 { return &ValidationError{ Field: "age", Message: "must be positive", } } return nil } func main() { // Basic check if err := validateAge(-5); err != nil { // Type assertion for custom error var valErr *ValidationError if errors.As(err, &valErr) { fmt.Printf("Field: %s\n", valErr.Field) } } // Sentinel error comparison err := findUser("unknown") if errors.Is(err, ErrNotFound) { fmt.Println("User not found") } } func findUser(id string) error { // Error wrapping with context return fmt.Errorf("findUser %s: %w", id, ErrNotFound) } ``` การห่อด้วย `%w` ผูก error เข้าด้วยกันโดยที่ยังตรวจสอบ error ดั้งเดิมได้ ### 12. ใช้ panic และ recover เมื่อใด `panic` หยุดการทำงานปกติและคลายสแตก ส่วน `recover` จับ panic ใน `defer` และทำให้โปรแกรมทำงานต่อได้ ```go // panic_recover.go package main import "fmt" func safeOperation() (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("recovered from panic: %v", r) } }() riskyOperation() return nil } func riskyOperation() { // Simulates an operation that can panic panic("something went wrong") } // Legitimate use case: initialization validation func MustCompileRegex(pattern string) *Regexp { r, err := regexp.Compile(pattern) if err != nil { panic(err) // Programming error } return r } func main() { err := safeOperation() if err != nil { fmt.Println("Recovered error:", err) } fmt.Println("Program continues") } ``` > **กฎทอง** > > panic ควรใช้เฉพาะกับข้อผิดพลาดเชิงโปรแกรม (invariant ที่ละเมิด) สำหรับ error ที่คาดเดาได้ (ไฟล์หาย ปัญหาเครือข่าย) ควรคืน error เสมอ ## struct เมธอด และ embedding ### 13. receiver แบบค่าและแบบพอยน์เตอร์ต่างกันอย่างไร receiver แบบค่ารับสำเนาของ struct ส่วน receiver แบบพอยน์เตอร์รับการอ้างอิงและสามารถแก้ไขข้อมูลต้นฉบับได้ ```go // receivers.go package main import "fmt" type Counter struct { value int } // Value receiver - works on copy func (c Counter) GetValue() int { return c.value } // Pointer receiver - modifies original func (c *Counter) Increment() { c.value++ } // Pointer receiver for large structs (avoids copy) type LargeStruct struct { data [1000]int } func (l *LargeStruct) Process() { // Avoids copying 8000 bytes } func main() { c := Counter{value: 0} c.Increment() // Go automatically converts fmt.Println(c.GetValue()) // 1 // Careful with interfaces var _ fmt.Stringer = &c // OK if method on *Counter } ``` หลักการ: หากเมธอดใดใช้ receiver แบบพอยน์เตอร์ ทุกเมธอดในชนิดเดียวกันควรใช้ receiver แบบพอยน์เตอร์เพื่อความสม่ำเสมอ ### 14. embedding ใน Go ทำงานอย่างไร embedding คือการนำชนิดหนึ่งมาฝังในอีกชนิดหนึ่งเพื่อรับเมธอดและฟิลด์มาด้วย ไม่ใช่การสืบทอดแบบดั้งเดิมแต่เป็นการประกอบ (composition) ```go // embedding.go package main import "fmt" type Logger struct { prefix string } func (l *Logger) Log(msg string) { fmt.Printf("[%s] %s\n", l.prefix, msg) } // Embedding Logger type Service struct { *Logger // Pointer embedding name string } func NewService(name string) *Service { return &Service{ Logger: &Logger{prefix: name}, name: name, } } func main() { svc := NewService("API") // Promoted method - direct access svc.Log("Starting") // Explicit access also works svc.Logger.Log("Explicit") // Promoted field fmt.Println(svc.prefix) // "API" } ``` embedding มอบความยืดหยุ่นในการประกอบโครงสร้างโดยไม่ติดข้อจำกัดของการสืบทอด ### 15. ใช้รูปแบบ singleton ใน Go อย่างไร แพ็กเกจ `sync` มี `sync.Once` สำหรับรับประกันให้การเริ่มต้นทำงานเพียงครั้งเดียวแม้มี goroutine หลายตัวเรียกพร้อมกัน ```go // singleton.go package main import ( "fmt" "sync" ) type Database struct { connectionString string } var ( instance *Database once sync.Once ) func GetDatabase() *Database { once.Do(func() { fmt.Println("Single initialization") instance = &Database{ connectionString: "postgres://...", } }) return instance } func main() { // Concurrent calls - single initialization var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() db := GetDatabase() fmt.Printf("Instance: %p\n", db) }() } wg.Wait() } ``` `sync.Once` ปลอดภัยต่อการทำงานพร้อมกันและสง่างามกว่าการใช้ mutex ร่วมกับ double-check locking ## context และการยกเลิกงาน ### 16. แพ็กเกจ context มีไว้เพื่ออะไร แพ็กเกจ `context` จัดการ deadline สัญญาณยกเลิก และค่าผูกกับคำขอตลอดต้นไม้การเรียกใช้ ```go // context.go package main import ( "context" "fmt" "time" ) func main() { // Context with timeout ctx, cancel := context.WithTimeout( context.Background(), 2*time.Second, ) defer cancel() // Always call cancel result := make(chan string, 1) go func() { // Simulate long operation time.Sleep(3 * time.Second) result <- "completed" }() select { case res := <-result: fmt.Println(res) case <-ctx.Done(): fmt.Println("Timeout:", ctx.Err()) } } // Propagation through functions func fetchData(ctx context.Context, url string) ([]byte, error) { // Early check if ctx.Err() != nil { return nil, ctx.Err() } req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return nil, err } // HTTP client respects context resp, err := http.DefaultClient.Do(req) // ... } ``` ฟังก์ชันที่อาจใช้เวลานานควรรับ `context.Context` เป็นพารามิเตอร์ตัวแรกเสมอ ### 17. จัดการ graceful shutdown ของโปรแกรมอย่างไร สัญญาณระบบเช่น SIGINT และ SIGTERM สามารถดักจับเพื่อปิดโปรแกรมอย่างสะอาด ```go // graceful_shutdown.go package main import ( "context" "fmt" "os" "os/signal" "syscall" "time" ) func main() { // Context cancelled on signal ctx, stop := signal.NotifyContext( context.Background(), syscall.SIGINT, syscall.SIGTERM, ) defer stop() // Start server server := startServer() // Wait for signal <-ctx.Done() fmt.Println("\nShutting down...") // Timeout for graceful shutdown shutdownCtx, cancel := context.WithTimeout( context.Background(), 5*time.Second, ) defer cancel() if err := server.Shutdown(shutdownCtx); err != nil { fmt.Println("Shutdown error:", err) } fmt.Println("Shutdown complete") } ``` รูปแบบนี้ช่วยให้การเชื่อมต่อที่ทำงานอยู่ปิดอย่างถูกต้องก่อนที่โปรแกรมจะหยุด ## การทดสอบและ benchmark ### 18. เขียนเทสต์ใน Go อย่างไร แพ็กเกจ `testing` ที่ติดมาให้ฟังก์ชันการทำงานพื้นฐาน เทสต์อยู่ในไฟล์ `*_test.go` ```go // calculator_test.go package calculator import "testing" func TestAdd(t *testing.T) { result := Add(2, 3) if result != 5 { t.Errorf("Add(2, 3) = %d; want 5", result) } } // Table-driven tests func TestAddTableDriven(t *testing.T) { tests := []struct { name string a, b int expected int }{ {"positive", 2, 3, 5}, {"negative", -1, -1, -2}, {"mixed", -1, 5, 4}, {"zero", 0, 0, 0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := Add(tt.a, tt.b) if result != tt.expected { t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected) } }) } } ``` table-driven test คือรูปแบบเชิงสำนวนของ Go สำหรับทดสอบหลายกรณีพร้อมกัน ### 19. เขียน benchmark อย่างไร benchmark ใช้ `testing.B` และรันด้วย `go test -bench` ```go // benchmark_test.go package main import ( "strings" "testing" ) func BenchmarkStringConcat(b *testing.B) { for i := 0; i < b.N; i++ { var s string for j := 0; j < 100; j++ { s += "a" } } } func BenchmarkStringBuilder(b *testing.B) { for i := 0; i < b.N; i++ { var sb strings.Builder for j := 0; j < 100; j++ { sb.WriteString("a") } _ = sb.String() } } // Typical results: // BenchmarkStringConcat-8 50000 28000 ns/op // BenchmarkStringBuilder-8 1000000 1200 ns/op ``` benchmark ช่วยเปิดเผยความต่างของประสิทธิภาพระหว่างการนำไปใช้แต่ละแบบ ## Generics (Go 1.18+) ### 20. ใช้ generics ใน Go อย่างไร Go 1.18 เพิ่มพารามิเตอร์ของชนิดข้อมูล จึงเขียนโค้ดแบบทั่วไปได้โดยยังคงความปลอดภัยของชนิด ```go // generics.go package main import "fmt" // Generic function func Map[T, U any](slice []T, fn func(T) U) []U { result := make([]U, len(slice)) for i, v := range slice { result[i] = fn(v) } return result } // Custom type constraint type Number interface { int | int64 | float64 } func Sum[T Number](values []T) T { var sum T for _, v := range values { sum += v } return sum } // Generic type type Stack[T any] struct { items []T } func (s *Stack[T]) Push(item T) { s.items = append(s.items, item) } func (s *Stack[T]) Pop() (T, bool) { if len(s.items) == 0 { var zero T return zero, false } item := s.items[len(s.items)-1] s.items = s.items[:len(s.items)-1] return item, true } func main() { // Usage doubled := Map([]int{1, 2, 3}, func(n int) int { return n * 2 }) fmt.Println(doubled) // [2 4 6] fmt.Println(Sum([]int{1, 2, 3, 4, 5})) // 15 stack := &Stack[string]{} stack.Push("hello") stack.Push("world") val, _ := stack.Pop() fmt.Println(val) // "world" } ``` generics ลดความจำเป็นในการเขียนโค้ดซ้ำหรือใช้ `interface{}` ## โมดูลและ dependency ### 21. ระบบโมดูลของ Go ทำงานอย่างไร โมดูล Go จัดการ dependency ด้วยการกำกับเวอร์ชันแบบ semantic ไฟล์ `go.mod` กำหนดโมดูลและ dependency ของมัน ```go // go.mod example module github.com/user/myproject go 1.21 require ( github.com/gin-gonic/gin v1.9.1 github.com/lib/pq v1.10.9 ) // Essential commands: // go mod init github.com/user/project // go mod tidy - clean dependencies // go get package@v1.2.3 - add/update // go mod vendor - copy locally ``` ```bash # Updating dependencies go get -u ./... # All dependencies go get -u=patch ./... # Patches only ``` ไฟล์ `go.sum` เก็บค่าเช็คซัมแบบเข้ารหัสเพื่อยืนยันความถูกต้องของ dependency ### 22. โครงสร้างโปรเจกต์ Go ควรเป็นอย่างไร โครงสร้างมาตรฐานยึดตามธรรมเนียมของชุมชนโดยไม่บังคับกฎเข้มงวด ``` myproject/ ├── cmd/ │ └── api/ │ └── main.go # Entry point ├── internal/ # Private to module │ ├── handler/ │ ├── service/ │ └── repository/ ├── pkg/ # Reusable external code ├── go.mod ├── go.sum └── README.md ``` โฟลเดอร์ `internal` มีคุณสมบัติพิเศษ คือเนื้อหาภายในไม่สามารถถูกนำเข้าโดยโมดูลอื่น ## คำถามขั้นสูง ### 23. garbage collector ของ Go ทำงานอย่างไร Go ใช้ garbage collector แบบ tricolor mark-and-sweep ที่ทำงานพร้อมกันและออกแบบมาเพื่อรองรับ latency ต่ำ ```go // gc_optimization.go package main import "runtime" func main() { // GC configuration // GOGC=100 (default) - triggers GC when heap doubles // Force GC runtime.GC() // Memory statistics var stats runtime.MemStats runtime.ReadMemStats(&stats) println("Alloc:", stats.Alloc) println("NumGC:", stats.NumGC) println("PauseTotalNs:", stats.PauseTotalNs) } // Optimization techniques // 1. Reuse allocations with sync.Pool // 2. Pre-allocate slices with make([]T, 0, cap) // 3. Avoid repeated string/[]byte conversions // 4. Use pointers for large structs ``` ตัวแปรสภาพแวดล้อม `GODEBUG=gctrace=1` แสดง trace ของ GC ### 24. อธิบายตัวจัดตารางเวลา (scheduler) ของ Go ตัวจัดตารางเวลาของ Go ใช้โมเดล M:N ที่จับคู่ goroutine จำนวน N ตัวเข้ากับ thread ระบบจำนวน M ตัว มีสามองค์ประกอบ ได้แก่ G (goroutine) M (thread) และ P (โปรเซสเซอร์เชิงตรรกะ) ```go // scheduler.go package main import ( "fmt" "runtime" ) func main() { // Number of logical processors (P) fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0)) // Number of active goroutines fmt.Println("NumGoroutine:", runtime.NumGoroutine()) // Yield processor to other goroutines runtime.Gosched() // M:P:G model // - G: goroutine (lightweight stack ~2KB) // - M: OS thread (machine) // - P: logical processor (execution context) // // Each P has a local queue of Gs // Work stealing when queue is empty } ``` ตั้งแต่ Go 1.14 ตัวจัดตารางเวลาเป็นแบบ preemptive จึงไม่ปล่อยให้ goroutine ตัวใดยึดครอง P ทั้งหมด ### 25. ปรับประสิทธิภาพใน Go อย่างไร การปรับประสิทธิภาพเริ่มต้นจากการทำ profiling เพื่อหาคอขวด ```go // profiling.go package main import ( "os" "runtime/pprof" ) func main() { // CPU profiling f, _ := os.Create("cpu.prof") pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() // Code to profile... // Memory profiling mf, _ := os.Create("mem.prof") defer mf.Close() pprof.WriteHeapProfile(mf) } // Analysis: go tool pprof cpu.prof // Common optimization techniques: // 1. Avoid allocations in hot loops // 2. Use sync.Pool for reusable objects // 3. Prefer []byte over string for mutations // 4. Use bufio for I/O // 5. Batch database operations ``` > **กฎของการปรับแต่ง** > > ควรวัดผลก่อนแล้วจึงปรับแต่ง การ profiling มักเปิดเผยคอขวดที่ไม่คาดคิด ## บทสรุป คำถามทั้ง 25 ข้อนี้ครอบคลุมแนวคิดพื้นฐานที่ใช้ประเมินในการสัมภาษณ์ Go: **รายการเตรียมตัว:** - ✅ ความเชี่ยวชาญใน goroutine และ channel - ✅ เข้าใจ interface แบบนัย - ✅ จัดการ error ตามแนวทางของ Go - ✅ ใช้ context อย่างถูกต้อง - ✅ รูปแบบการทำงานพร้อมกัน (mutex, worker pool) - ✅ การทดสอบและ benchmark - ✅ ความรู้เรื่อง generics ตั้งแต่ Go 1.18+ กุญแจสู่ความสำเร็จในการสัมภาษณ์ Go คือ การแสดงความเข้าใจในการแลกเปลี่ยนระหว่างความเรียบง่ายกับประสิทธิภาพ และรู้ว่าเมื่อใดควรเลือกใช้รูปแบบการทำงานพร้อมกันแบบใด --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/go/top-25-go-interview-questions