เริ่มจากศูนย์: ติดตั้ง Hello World ถึง goroutine + channel
สมมติคุณสั่งน้องว่า:
น้องทำตามได้ นั่นแหละคือ โปรแกรม — แค่เราเปลี่ยนจากภาษาคนเป็นภาษาที่คอมเข้าใจ
ภาษา Go ก็คือภาษาหนึ่งที่เราใช้สั่งคอม เหมือนภาษาไทย อังกฤษ แต่เอาไว้คุยกับคอม
ให้นึกภาพแบบนี้:
คนสร้าง Go คือทีม Google เขารำคาญว่าภาษาเดิมๆ ช้า เขียนยาก เลยสร้างภาษาใหม่ที่:
ใช้จริงที่ไหน? Docker, Kubernetes, ระบบหลังบ้านของ LINE, Shopee, TikTok ใช้ Go หมด
ตอนนี้ (ตุลาคม 2026) เวอร์ชันใหม่สุดคือ Go 1.27 ออกเมื่อ 19 สิงหาคม 2026
ไม่ต้องตกใจถ้าคุณเคยเห็นเลข 1.22, 1.23 ในคลิปเก่าๆ มันคืออันเดียวกันแค่เก่ากว่า วิธีเขียนพื้นฐานเหมือนกันหมด เล่มนี้จะบอกจุดที่เวอร์ชันใหม่ทำง่ายขึ้นด้วยภาษาง่ายๆ
ตอบ: ไม่ต้องมีเลย ขอแค่:
ถ้าคุณเคยใช้เครื่องคิดเลข คุณก็เรียน Go ได้แล้ว
ถ้าคุณเพิ่งเริ่มเรียนวันนี้ คุณโชคดี เพราะ Go 1.26 และ 1.27 (ต้นปี - สิงหาคม 2026) ทำให้ชีวิตมือใหม่ง่ายขึ้นเยอะ อธิบายแบบบ้านๆ:
new(ค่า)เมื่อก่อนถ้าอยากได้กล่องพร้อมที่อยู่ต้องทำ 2 บรรทัด:
ตอนนี้ทำบรรทัดเดียวจบ:
แปลไทย: new(300) = "สร้างกล่องใส่เลข 300 พร้อมบอกที่อยู่มาเลย" จำแค่นี้พอ เดี๋ยวเจออีกทีตอน struct
สมมติมีกล่องซ้อนกล่อง เมื่อก่อนต้องแกะทีละชั้น ตอนนี้ใส่ทีเดียวได้เลย:
type Habitat struct {
Burrow string
}
type Gopher struct {
Name string
Habitat // ฝังกล่อง Habitat ไว้ข้างใน
}
// Go 1.27 ทำแบบนี้ได้เลย ไม่ต้องพิมพ์ Habitat{...} ซ้อนอีกชั้น
g := Gopher{
Name: "Gopher",
Burrow: "Burrow #42",
}เหมือนกรอกฟอร์ม จากที่ต้องเปิดซองในซอง ตอนนี้เขียนรวดเดียวจบ
เมื่อก่อนถ้าใช้ฟังก์ชันแบบครอบจักรวาล (generic) ต้องบอกชนิดเอง ตอนนี้ Go เดาให้:
// Go 1.27 เดา T = int ให้เอง ไม่ต้องพิมพ์ [int] ย้ำ
ch := make(chan func(int) string, 1)
ch <- GenericFormatter // ส่งฟังก์ชันเข้า channel ได้เลยมือใหม่จำแค่ว่า: ถ้าเห็นโค้ดส่งฟังก์ชันเข้า channel แล้วไม่มี [ ] ต่อท้าย ไม่ต้องตกใจ มันคือของใหม่ที่ทำให้สั้นลงนั่นเอง
go fixGo 1.26 รื้อ go fix ใหม่หมด เหมือนมีครูมานั่งแก้การบ้านให้:
มันจะช่วยเปลี่ยนโค้ดเก่าให้เป็นแบบใหม่เอง เช่น เปลี่ยนวิธีเขียน slice, วิธีใช้ atomic ให้ ไม่ต้องจำเองทั้งหมด มือใหม่ใช้คู่กับ:
httptest.NewTestServer + testing/synctest ไว้ซ้อมเขียนเว็บโดยไม่ต้องต่อเน็ตจริง (ขั้นสูง ข้ามก่อนได้)go1.27.1)
ถ้าเลือกเอง:
macOS ARM64macOS x86-64Windows x86-64เช็คว่าสำเร็จไหม เปิด Terminal (Mac) หรือ PowerShell (Windows) พิมพ์:
ถ้าขึ้นแบบนี้คือผ่าน (เลขตรงนี้ต้องเป็น 1.27 ขึ้นไปถึงจะเป็นปัจจุบัน):
go version go1.27.1 darwin/arm64
เลขเวอร์ชันต่างกันไม่เป็นไร แต่ถ้าขึ้น go1.22 หรือ go1.20 แปลว่าคุณลงไว้นานแล้ว แนะนำให้โหลดใหม่จากเว็บจะดีที่สุด
ถ้าขึ้น command not found = ติดตั้งยังไม่เสร็จ ให้ปิดหน้าต่างแล้วเปิดใหม่
Go กด Install อันของ Googleพิมพ์ใน Terminal ทีละบรรทัด:
อธิบาย:
mkdir hello = สร้างโฟลเดอร์ชื่อ hello (เหมือนสร้างแฟ้ม)cd hello = เดินเข้าไปในโฟลเดอร์go mod init hello = บอก Go ว่า "โฟลเดอร์นี้จะเป็นโปรเจกต์นะ" จะได้ไฟล์ go.mod มาหนึ่งไฟล์
สร้างไฟล์ชื่อ main.go ในโฟลเดอร์ hello แล้วพิมพ์ตามนี้เป๊ะๆ:
รันด้วยคำสั่ง:
จะเห็นคำว่า สวัสดี Go! ขึ้นมา ยินดีด้วย! คุณเขียน Go ได้แล้ว!
แปลว่า "ไฟล์นี้เป็นโปรแกรมหลักที่จะรัน" เหมือนหน้าปกหนังสือ ทุกโปรแกรมต้องมีบรรทัดนี้
แปลว่า "ขอยืมเครื่องมือชื่อ fmt มาหน่อย" fmt ย่อจาก format เป็นกล่องเครื่องมือสำหรับพิมพ์ข้อความ ถ้าไม่ import บรรทัดนี้ เราจะพิมพ์อะไรออกจอไม่ได้
แปลว่า "เริ่มทำงานตรงนี้นะ" คอมจะวิ่งมาหา main ก่อนเสมอ เหมือนประตูทางเข้าบ้าน
fmt = หยิบกล่องเครื่องมือมา.Println = เลือกอุปกรณ์ "พิมพ์แล้วขึ้นบรรทัดใหม่" (Print Line)("สวัสดี Go!") = ข้อความที่จะพิมพ์ ข้อความต้องอยู่ใน " " เสมอปิดประตู จบการทำงาน
ลองเล่น:
เปลี่ยนข้อความใน " " เป็นชื่อตัวเองแล้ว go run . ใหม่สิ
เอกสาร https://go.dev/doc/effective_go สอนไว้เยอะ แต่สำหรับมือใหม่จำแค่ 3 ข้อนี้ก่อน:
ใน Go เราไม่ต้องมานั่งเคาะ space เอง มีคำสั่ง:
มันจะจัดย่อหน้า วงเล็บ ช่องว่างให้สวยเอง เหมือนมีแม่บ้านจัดห้องให้
ใน VS Code แค่กด Save มันก็ fmt ให้อัตโนมัติแล้ว
ตัวอย่าง เครื่องจะแก้จากแบบรกๆ:
เป็นแบบสวยๆ:
เราไม่ต้องทำเอง!
Owner, Addowner, addbufio.Reader ดีกว่า bufio.BufReader (ไม่ต้องย้ำคำว่า Buf)userName ไม่ใช่ user_nameReader (นักอ่าน), Writer (นักเขียน)เหมือนตั้งชื่อลูก ตั้งดีเรียกง่าย ตั้งยาวเรียกยาก
แบบนี้ถูก:
แบบนี้ผิด (ห้ามทำ):
เหตุผล: Go จะแอบเติม ; ให้ตอนขึ้นบรรทัดใหม่ ทำให้พัง เราเลยต้องเอา { ไว้บรรทัดเดียวกัน จำแค่นี้พอ
นึกถึงกล่องลัง 1 ใบ แปะป้ายชื่อไว้ ข้างในใส่ของได้ 1 อย่าง เปลี่ยนของข้างในได้ตลอด นั่นคือ ตัวแปร (variable)
ใน Go สร้างกล่องแบบนี้:
var = "จะสร้างกล่องแล้วนะ"age = ชื่อป้ายแปะกล่องint = บอกว่ากล่องนี้ใส่ได้แค่ตัวเลขจำนวนเต็ม (integer)age = 20 = เอาเลข 20 ใส่กล่อง| ชนิด | แปลว่า | ตัวอย่าง |
|---|---|---|
int | เลขจำนวนเต็ม | 1, -5, 100 |
float64 | เลขทศนิยม | 3.14, -0.5 |
string | ข้อความ | "สวัสดี", "abc" |
bool | จริง/เท็จ | true, false |
:= (ใช้บ่อยที่สุด)คน Go ขี้เกียจพิมพ์ var เลยใช้ := แปลว่า "สร้างกล่อง + ใส่ของเลย + เดาชนิดให้ด้วย":
name := "สมชาย" // Go รู้เองว่าเป็น string
age := 20 // Go รู้เองว่าเป็น int
height := 175.5 // Go รู้เองว่าเป็น float64ถ้าสร้างกล่องแล้วยังไม่ใส่ของ Go จะใส่ของเปล่าๆ ให้เอง ไม่ปล่อยขยะ:
int ได้ 0string ได้ "" (ว่าง)bool ได้ falseดีมาก! ไม่ต้องกลัวลืมใส่ค่าแล้วโปรแกรมระเบิด
แบบฝึกหัดบทนี้:
เรารู้จัก fmt.Println แล้ว มาดูญาติของมัน:
fmt.Println("สวัสดี", 23) // พิมพ์หลายอย่างคั่นด้วยช่องว่าง + ขึ้นบรรทัดใหม่
fmt.Print("สวัสดี", 23) // เหมือนกันแต่ไม่ขึ้นบรรทัดใหม่
fmt.Printf("อายุ %d ปี\n", 23) // พิมพ์แบบควบคุมเอง %d=เลข %s=ข้อความ%d = จองที่ไว้ใส่เลขจำนวนเต็ม (d = digit)%s = จองที่ไว้ใส่ข้อความ (s = string)%v = จองที่ไว้ใส่อะไรก็ได้ (v = value) ใช้บ่อยสุดตอนหัด\n = ขึ้นบรรทัดใหม่name := "สมชาย"
age := 20
fmt.Printf("ผมชื่อ %s อายุ %d ปี\n", name, age)
fmt.Printf("%v\n", name) // %v ก็พิมพ์ได้เหมือนกันจำแค่ Println กับ Printf ก็พอใช้ 80% แล้ว
ชีวิตเราตัดสินใจแบบนี้ทุกวัน: "ถ้าฝนตกก็เอาร่ม ถ้าไม่ตกก็ไม่เอา" คอมก็เหมือนกัน
อ่านเป็นไทย: "ถ้า age มากกว่าเท่ากับ 18 ก็พิมพ์..."
score := 85
if score >= 80 {
fmt.Println("ได้ A")
} else if score >= 70 {
fmt.Println("ได้ B")
} else if score >= 60 {
fmt.Println("ได้ C")
} else {
fmt.Println("ตก")
}f, err := os.Open("a.txt")
if err != nil {
fmt.Println("เปิดไม่ได้:", err)
return
}
fmt.Println("เปิดได้แล้ว ใช้ f ต่อได้เลย")แปลว่า: "ถ้า error ไม่ใช่ค่าว่าง (nil = ว่าง) แปลว่าพัง ให้จบเลย ไม่ต้องทำต่อ" โค้ดจะได้ไม่ซ้อนกันเป็นพีระมิด อ่านง่าย
ศัพท์: nil = ว่างเปล่า ไม่มีอะไรเลย, != = ไม่เท่ากับ
แบบฝึกหัด: เขียนโปรแกรมอายุ ถ้าน้อยกว่า 13 พิมพ์ "เด็ก", น้อยกว่า 20 พิมพ์ "วัยรุ่น", นอกนั้น "ผู้ใหญ่"
ภาษาอื่นมี while, for, do-while เยอะแยะ Go มีแค่ for อันเดียวแต่ทำได้ 3 แบบ:
อ่านทีละส่วน for i := 0; i < 5; i++:
i := 0 = เริ่มที่ 0i < 5 = ถ้ายังน้อยกว่า 5 ก็ทำต่อi++ = เสร็จรอบนึงให้บวกเพิ่ม 1 (++ = +1)เหมือน "วิ่ง 5 รอบสนาม นับรอบไปด้วย"
ถ้าไม่มี break จะวนไม่จบ คอมค้างนะ!
fruits := []string{"แอปเปิ้ล", "กล้วย", "ส้ม"}
for index, value := range fruits {
fmt.Printf("ช่องที่ %d คือ %s\n", index, value)
}range = "ไล่ทีละชิ้น"index = เลขช่อง (เริ่ม 0)value = ของในช่องนั้นถ้าอยากได้แค่ค่า ไม่เอาหมายเลข ใช้ _ ทิ้งไป (_ = ถังขยะ):
แบบฝึกหัด: ใช้ for พิมพ์สูตรคูณแม่ 2 (2x1=2 ... 2x12=24)
สมมติคุณต้องบวกเลข 10 ครั้ง จะเขียน a+b ซ้ำ 10 รอบไหม? เหนื่อย! เราสร้าง เครื่องจักร ขึ้นมาตัวหนึ่ง โยนของเข้าไป มันคายผลออกมา เรียกใช้กี่ครั้งก็ได้ นั่นคือฟังก์ชัน
func Add(a int, b int) int {
return a + b
}
func main() {
fmt.Println(Add(2, 3)) // 5
fmt.Println(Add(10, 20)) // 30
}อธิบาย:
func = จะสร้างเครื่องจักรแล้วAdd = ชื่อเครื่อง(a int, b int) = ช่องใส่ของเข้า 2 ช่อง รับแต่ intint หลังวงเล็บ = ชนิดของที่จะคายออกreturn a + b = คายผลออกมาภาษาอื่นคืนได้ค่าเดียว แต่ Go คืนได้หลายค่า เหมาะกับการคืน "ผล + error":
func Divide(a, b float64) (float64, string) {
if b == 0 {
return 0, "หารด้วยศูนย์ไม่ได้"
}
return a / b, ""
}
func main() {
result, msg := Divide(10, 2)
fmt.Println(result, msg) // 5 ""
result2, msg2 := Divide(10, 0)
fmt.Println(result2, msg2) // 0 หารด้วยศูนย์ไม่ได้
}มือใหม่จำแค่นี้: สร้างด้วย func เรียกใช้ด้วย ชื่อ(...)
แบบฝึกหัด: สร้างฟังก์ชัน Greet(name string) string คืนค่า "สวัสดี " + name
var a [3]int // ตู้ 3 ช่องใส่เลขได้เท่านั้น
a[0] = 10
a[1] = 20
a[2] = 30
fmt.Println(a) // [10 20 30]เลขใน [ ] คือจำนวนช่อง เปลี่ยนไม่ได้! [3]int กับ [5]int ถือว่าคนละชนิดกัน
ข้อเสีย: ถ้าสร้าง 3 ช่องแล้วอยากได้ช่องที่ 4 ทำไม่ได้ ต้องสร้างตู้ใหม่ คนเลยไม่ค่อยใช้ Array
s := []int{1, 2, 3} // ไม่ต้องบอกจำนวนช่อง
s = append(s, 4, 5) // append = ต่อท้าย
fmt.Println(s) // [1 2 3 4 5]จำสูตร: s = append(s, ของที่จะเพิ่ม) ต้องมี s = ข้างหน้าด้วยนะ! ลืมบ่อยที่สุด
หั่น slice ได้ด้วย (slicing):
phone := map[string]string{
"แม่": "081-111-1111",
"พ่อ": "082-222-2222",
}
fmt.Println(phone["แม่"]) // 081-111-1111
phone["เพื่อน"] = "083-333-3333" // เพิ่ม
delete(phone, "พ่อ") // ลบเช็คว่ามีชื่อนี้ไหมด้วย comma-ok (ถ้าไม่มีจะได้ค่าว่าง ต้องแยกให้ออก):
เบอร์, มีไหม := phone["ลุง"]
if มีไหม {
fmt.Println("เจอแล้ว", เบอร์)
} else {
fmt.Println("ไม่มีชื่อนี้")
}เปรียบเทียบให้จำ:
if เหมาะกับ 2-3 ทาง ถ้าหลายทางใช้ switch สวยกว่า:
day := "จันทร์"
switch day {
case "จันทร์":
fmt.Println("วันแรกของสัปดาห์ สู้ๆ")
case "ศุกร์":
fmt.Println("พรุ่งนี้หยุดแล้ว!")
case "เสาร์", "อาทิตย์": // หลายค่าคั่นด้วย comma ได้
fmt.Println("วันหยุด!")
default: // นอกนั้น
fmt.Println("วันธรรมดา")
}switch ของ Go ไม่ต้องมี break มันหยุดเอง ไม่ไหลทะลุเหมือนภาษา C
แบบไม่ใส่ค่าก็ได้ (เท่ากับ switch true):
ตัวแปร 1 ตัวเก็บได้ 1 อย่าง แต่คน 1 คนมีทั้งชื่อ อายุ ส่วนสูง จะทำไง? เอากล่องหลายใบมัดรวมกันเรียก struct:
type Person struct {
Name string
Age int
Height float64
}
func main() {
p := Person{Name: "สมชาย", Age: 20, Height: 175.5}
fmt.Println(p.Name) // สมชาย
fmt.Println(p.Age) // 20
}type Person struct = ออกแบบฟอร์มชื่อ PersonName string = ช่องชื่อใส่ข้อความได้Name) = คนอื่นเห็นได้ ขึ้นต้นตัวเล็ก = เห็นแค่ใน package เดียวกันfunc (p Person) Greet() string {
return "สวัสดี ผมชื่อ " + p.Name
}
func main() {
p := Person{Name: "สมชาย"}
fmt.Println(p.Greet())
}(p Person) ข้างหน้าคือบอกว่า "ฟังก์ชันนี้เป็นของ Person นะ" เรียกใช้ด้วย p.Greet() แทน Greet(p)
ถ้า method ต้องแก้ค่าใน struct ต้องใช้ pointer *:
จำง่ายๆ: แค่อ่านใช้ธรรมดา จะแก้ต้องมี *
ใน Go ไม่มี try-catch แบบภาษาอื่น แต่ใช้วิธีตรงๆ: ฟังก์ชันไหนพังได้ให้คืน error มาด้วย แล้วเช็คทันที
f, err := os.Open("ไม่มีไฟล์นี้.txt")
if err != nil {
fmt.Println("เปิดไม่ได้:", err)
return
}
fmt.Println("เปิดได้แล้ว")err = ตัวแปรเก็บความผิดพลาดnil = ว่าง (แปลว่าไม่ผิด)err != nil = "ถ้าไม่ว่าง แปลว่ามีเรื่อง!"สร้าง error เอง:
import "errors"
func CheckAge(age int) error {
if age < 0 {
return errors.New("อายุติดลบไม่ได้")
}
return nil // nil = ปกติดี
}defer แปลว่า "เลื่อนไปทำตอนจะจบ" เหมาะกับงานเก็บกวาด เช่น เปิดไฟล์แล้วต้องปิด:
func ReadFile() {
f, _ := os.Open("a.txt")
defer f.Close() // สัญญาว่าก่อนจบฟังก์ชันจะปิดไฟล์ให้ ไม่ว่าจะออกทางไหน
// ... อ่านไฟล์ ...
// ... ถ้ามี return ตรงไหน f.Close() ก็ยังรันอยู่ดี
}เหมือนบอกเพื่อนว่า "ไปเที่ยวก่อนนะ ก่อนกลับจะล็อกประตูให้" ไม่ว่าจะกลับทางไหนก็ล็อกแน่
ถ้า defer หลายอัน จะทำจากหลังมาหน้า (LIFO):
สมมติคุณเปิดร้านอาหาร มีลูกค้า 3 โต๊ะ ถ้ามีพ่อครัวคนเดียวต้องทำทีละโต๊ะ โต๊ะ 3 รอนาน
วิธีแก้: จ้างพ่อครัว 3 คนทำพร้อมกัน! ใน Go พ่อครัว 1 คน = goroutine 1 ตัว
package main
import (
"fmt"
"time"
)
func Cook(name string) {
for i := 0; i < 3; i++ {
fmt.Println(name, "กำลังทำ...")
time.Sleep(200 * time.Millisecond)
}
}
func main() {
go Cook("พ่อครัว A") // จ้างเพิ่ม! ทำพร้อมกัน
Cook("พ่อครัว B") // คนเดิมทำ
}แค่เติม go ข้างหน้าก็จบ! ไม่ต้องสร้าง thread ยุ่งยากเหมือนภาษาอื่น เปิดเป็นหมื่นตัวยังไหว
พ่อครัวทำพร้อมกันแต่ต้องส่งจานให้กัน จะตะโกนก็มั่ว ต้องมี รางส่งจาน วางจานลงไป อีกคนหยิบไป นั่นคือ channel
คำขวัญของ Go:
func main() {
ch := make(chan string) // สร้างรางส่งข้อความ
go func() {
ch <- "ผัดกะเพราเสร็จแล้ว!" // <- คือวางจานลงราง (ส่ง)
}()
msg := <-ch // รับจานจากราง (รับ)
fmt.Println(msg)
}จำสัญลักษณ์:
ch <- "xxx" = ส่งเข้า (ลูกศรชี้เข้า channel)x := <-ch = รับออก (ลูกศรชี้ออกจาก channel)1. แบบไม่มีที่พัก (unbuffered) — วางแล้วต้องมีคนรับทันที เหมือนส่งลูกบอลมือต่อมือ:
ch := make(chan int) // ไม่ใส่เลข = ไม่มีที่พัก
go func() { ch <- 1 }()
fmt.Println(<-ch) // ต้องมีคนรอรับ ไม่งั้นค้าง2. แบบมีกล่องพัก (buffered) — วางทิ้งไว้ได้ถ้ากล่องไม่เต็ม เหมือนตู้พัสดุ:
มือใหม่เริ่มจากแบบที่ 1 ก่อนก็ได้ เข้าใจง่ายกว่า
func main() {
ch := make(chan int, 5)
go func() {
for i := 1; i <= 3; i++ {
ch <- i
}
close(ch) // ตะโกนบอกว่าหมดแล้ว! สำคัญมาก
}()
// range = หยิบไปเรื่อยๆ จนกว่าจะ close
for v := range ch {
fmt.Println("ได้รับ", v)
}
}closefor v := range ch รับจนหมดเอง ไม่ต้องนับสมมติมี 2 ราง ไม่รู้รางไหนมาก่อน ใช้ select นั่งเฝ้าทั้งคู่ อันไหนมาก่อนเอาอันนั้น:
select {
case v := <-ch1:
fmt.Println("ราง 1 มาแล้ว", v)
case v := <-ch2:
fmt.Println("ราง 2 มาแล้ว", v)
case <-time.After(1 * time.Second):
fmt.Println("รอ 1 วิแล้ว ไม่มีมาเลย เลิก!")
}เหมือนนั่งเฝ้า 2 เตา เตาไหนดังก่อนวิ่งไปเตานั้น + ตั้งนาฬิกาปลุกกันรอเก้อ
package main
import "fmt"
// โรงงานขั้นที่ 1: ผลิตเลข
func gen(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
// โรงงานขั้นที่ 2: ยกกำลังสอง
func sq(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}
func main() {
for n := range sq(gen(1, 2, 3, 4)) {
fmt.Println(n) // 1 4 9 16
}
}ของไหล gen → sq → พิมพ์ เป็นทอดๆ ไม่ต้องรอให้เสร็จทั้งล็อต
| ข้อความแดง | แปลว่า | แก้ยังไง |
|---|---|---|
all goroutines are asleep - deadlock! | ทุกคนรอของแต่ไม่มีใครส่ง | ลืม go หรือลืมคนส่ง |
send on closed channel | ส่งของเข้าราที่ปิดแล้ว | อย่าส่งหลัง close |
| โปรแกรมค้างไม่จบ | ลืม close เลยรอตลอดไป | ใส่ close ฝั่งคนส่ง |
ตรวจหางานแย่งกันด้วย:
นี่คือพลัง goroutine + channel ของจริง จากที่ต้องรอทีละตัว 5 ตัว = 5 รอบ กลายเป็นรอรอบเดียว:
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func fetchPrice(symbol string) int {
time.Sleep(time.Duration(rand.Intn(300)) * time.Millisecond)
return rand.Intn(1000)
}
func main() {
symbols := []string{"AAPL", "GOOG", "TSLA", "AMZN", "MSFT"}
type Result struct {
Symbol string
Price int
}
ch := make(chan Result, len(symbols))
var wg sync.WaitGroup // สมุดนับว่าเหลือกี่งาน
for _, s := range symbols {
wg.Add(1) // จดว่าเพิ่ม 1 งาน
go func(sym string) {
defer wg.Done() // เสร็จแล้วขีดออก 1
ch <- Result{sym, fetchPrice(sym)}
}(s)
}
go func() {
wg.Wait() // รอจนงานหมด
close(ch) // ปิดราง
}()
for r := range ch {
fmt.Printf("%s = %d\n", r.Symbol, r.Price)
}
}ลองรันดู จะเห็นว่าเร็วและลำดับมั่วๆ (ใครเสร็จก่อนพิมพ์ก่อน) นั่นแหละ concurrency!
ง่าย:
Println, Print, Printf%2 == 0 = คู่)กลาง:
5. เขียนฟังก์ชันบวก ลบ คูณ หาร
6. สร้าง slice เก็บชื่อเพื่อน 5 คนแล้ววนพิมพ์
7. สร้าง map เก็บคะแนนเพื่อนแล้วหาคนได้เยอะสุด
8. สร้าง struct Student มี Name Score + method ตัดเกรด
ยาก (Channels): 9. สร้าง 2 goroutine ส่งเลขเข้า channel เดียวกัน 10. ใช้ select ทำ timeout 2 วินาที
| คำ | แปลแบบบ้านๆ |
|---|---|
| โค้ด | ตัวหนังสือสั่งคอม |
| รัน | สั่งให้คอมทำ |
| ตัวแปร | กล่องใส่ของ |
| type | ชนิดของที่ใส่กล่องได้ |
| ฟังก์ชัน | เครื่องจักร ใส่ของเข้า คายผลออก |
| struct | แฟ้มรวมกล่องหลายใบ |
| array/slice | ตู้หลายช่อง / กระเป๋ายืดได้ |
| map | สมุดโทรศัพท์ |
| error | ใบแจ้งว่าเจ๊งตรงไหน |
| nil | ว่าง ไม่มีอะไร |
| goroutine | พ่อครัวเพิ่ม (ทำงานพร้อมกัน) |
| channel | รางส่งจานระหว่างพ่อครัว |
| close | ตะโกนบอกว่าของหมดแล้ว |
| select | นั่งเฝ้าหลายรางพร้อมกัน |
| defer | สัญญาว่าก่อนจบจะกลับมาทำ |
| gofmt | แม่บ้านจัดห้อง(โค้ด)ให้ |
| go fix | ครูแก้การบ้าน เปลี่ยนโค้ดเก่าเป็นแบบใหม่ให้ (ของใหม่ Go 1.26) |
| go vet | หมอตรวจโรค ตรวจว่าโค้ดมีที่ผิดไหม |
เล่มนี้เขียนใหม่สำหรับคนไม่เคยเขียน Go เลย จากเอกสาร Effective Go https://go.dev/doc/effective_go#channels — อัปเดตสำหรับ Go 1.27.1 (ตุลาคม 2026) — ถ้าอ่านแล้วงงตรงไหน ให้รันโค้ดตรงนั้นซ้ำ เปลี่ยนเลข เปลี่ยนข้อความดู แล้วจะเข้าใจเอง ขอให้สนุกกับ Go ครับ!