ภาษา Go ตอน 5 รู้จักกับ Go Routine

Chaiyarin Niamsuwan
odds.team
Published in
5 min readSep 9, 2018

จากตอนที่ 4 ที่ผ่านมา เราสามารถสร้าง Package ของเราเอง ไปให้คนอื่นใช้ หรือ จะนำมา Reuse ใช้เองได้แล้ว

และเรายังรู้วิธีนำ Package ดีๆ จากคนอื่นมาใช้งานด้วย เพื่อนๆ คนไหนยังไม่ได้อ่าน กดได้จาก Link ข้างล่างนี้นะครับ

ในตอนนี้ผมจะพาเพื่อนๆ ไปรู้จักกับ การใช้งาน Go Routine ว่ามันคืออะไรกันนะ ใช้งานยังไง และมันจำเป็นต้องใช้ในสถานการณ์ แบบไหนกัน มาลุยกันเลย

Go Routine คืออะไร ?

ลองนึกภาพตามผมนะครับ สมมติว่า เราต้องการจะซื้อ มาม่า ที่ร้านเซเว่น, ซื้อนาฬิกา ที่ห้างเซ็นทรัล, ซื้อผลไม้ที่สยามพารากอน, ซื้อรถที่โชว์รูม Toyota ทั้งหมดนี้ จะต้องเสร็จภายในวันเดียวกัน

เราก็ต้อง ทำทีละอย่าง ไปทีละ Step เนอะ ไป เซเว่น -> ไป เซ็นทรัล -> ไป สยามพารากอน -> โชว์รูม Toyota ภารกิจเราในวันนั้น จะเสร็จสมบูรณ์

ต้องไปไปทีละอย่างจนกว่าจะเสร็จ

จะสังเกตว่ามันค่อนข้างใช้เวลาเยอะ ในการทำอะไรคนเดียว ไปตั้ง 4 สถานที่ ตามลำดับ คำถามคือ มันมีวิธีไหมที่เราจะทำให้ เราซื้อของในแต่ละที่ เสร็จเร็วกว่านี้

มันมีครับ ก็ใช้คน อีก 1 คนไปซื้อของบางอย่างแทนเราที่สถานที่นั้น หรือจะใช้ 2 คนก็ได้ ไปคนละห้างเลย และพอทุกคนทำเสร็จก็เอาของมาให้เรา

ให้อีกคนมาช่วยเราทำงานให้เสร็จ งานเราจะได้ไวขึ้น

จะเห็นว่าแต่ละอย่างที่ไปซื้อมันสามารถทำไปพร้อมๆกันได้ นี่หละครับ Go Routine จะมาทำหน้าที่นี้เป็นตัวช่วยเราในการทำงานให้ไว้ขึ้น

Go Routine ทำงานยังไง ?

โดยปกติแล้ว เวลาเราเขียน Code ให้ทำงาน บนไฟล์ main.go ที่ผ่านมา เวลาสั่ง go run

[Filename main.go]package mainimport (
"fmt"
)
func main() {
fmt.Println("ซื้อแว่น ที่ เซเว่น");
fmt.Println("ซื้อนาฬิกา ที่ เซ็นทรัล");
fmt.Println("ซื้อผลไม้ ที่ สยามพารากอน");
fmt.Println("ซื้อรถ ที่ ศูนย์ Toyota");
}
[ผลลัพธ์จากการ Run]ซื้อแว่น ที่ เซเว่น
ซื้อนาฬิกา ที่ เซ็นทรัล
ซื้อผลไม้ ที่ สยามพารากอน
ซื้อรถ ที่ ศูนย์ Toyota
*** จะเห็นว่าโปรแกรม Run ทีละคำสั่งจากบนลงล่าง ***

ตัวโปรแกรมจะถูกทำงานไปทีละบรรทัด (Synchronous) ด้วยความเร็วของ CPU เพียง 1 Core เท่านั้น และมันก็ไม่ใช่เรื่องผิดปกติอะไร เพราะมันคือค่าพื้นฐาน (Default)

แต่การใช้ Go Routine ในบรรทัดนั้นๆ หรือ Function นั้นๆ จะทำให้ตัวโปรแกรมของเรามีการแบ่งการทำงาน ไปใช้ CPU อีก Core นึงได้ พูดง่ายๆ ก็คือ Run โปรแกรมของเรา ด้วยการใช้ CPU 2 Core

ซึ่งเราก็จะได้ใช้การประมวลผลของ CPU เราอย่างคุ้มค่าด้วย และทำให้ตัวโปรแกรมเราทำงานเร็วขึ้นด้วย เพราะมันคือการทำงานแบบคู่ขนานกันไป (Asynchronous)

Go Routine ใช้งานยังไง ?

จากหัวข้อข้างบน เราจะเห็นว่า เวลา Run ไฟล์ main.go โปรแกรมจะทำงานจากบนลงล่าง ทีละบรรทัด คราวนี้

เราจะใช้ Go Routine มาเป็นตัวช่วยให้โปรแกรมเราทำงานไวขึ้น โดยการใส่ Keyword “go” ไว้หน้าบรรทัดที่เราต้องการจะให้ มันทำงานคู่ขนานไปด้วยกัน แบบนี้ แล้วลองสั่ง go run เลย

[Filename main.go]package mainimport (
"fmt"
)
func main() {
fmt.Println("ซื้อแว่น ที่ เซเว่น");
fmt.Println("ซื้อนาฬิกา ที่ เซ็นทรัล");
go fmt.Println("ซื้อผลไม้ ที่ สยามพารากอน"); // ใส่ go ลงไป
fmt.Println("ซื้อรถ ที่ ศูนย์ Toyota");
}
[ผลลัพธ์จากการ Run]ซื้อแว่น ที่ เซเว่น
ซื้อนาฬิกา ที่ เซ็นทรัล
ซื้อรถ ที่ ศูนย์ Toyota

ปรากฎว่า “ซื้อผลไม้ ที่ สยามพารากอน” หายไปไม่แสดงซะอย่างนั้น ทำไมนะ ทำไมมันหายไป นั้นเป็นเพราะว่า

ใน Function main() นั้นทำงานเสร็จไปแล้ว แต่ นาย B หรือ บรรทัดที่เราใส่ Keyword go ไว้ด้านหน้านั้นยังทำงานไม่เสร็จ

เมื่อ Function main() ทำงานเสร็จก่อน มันเลยไม่ได้รอ นาย B นั้นทำเสร็จโปรแกรมเลยปิดตัวก่อนที่จะแสดงผลลัพธ์ “ซื้อผลไม้ ที่ สยามพารากอน”

คราวนี้เรามาลองแก้ Code ให้รอนาย B ทำงานเสร็จกันสักหน่อย โดยการหน่วงเวลารอ นาย B โดยเพิ่ม Code ไปแบบนี้ครับ

time.Sleep(1 * time.Second)

นี่คือ Code หน่วงเวลา 1 วินาที เพื่อรอ นาย B ทำงานเสร็จ ดังนั้นไฟล์ main.go ก็จะเป็นแบบนี้

[Filename main.go]package mainimport (
"fmt"
"time"
)
func main() {
fmt.Println("ซื้อแว่น ที่ เซเว่น");
fmt.Println("ซื้อนาฬิกา ที่ เซ็นทรัล");
go fmt.Println("ซื้อผลไม้ ที่ สยามพารากอน");
fmt.Println("ซื้อรถ ที่ ศูนย์ Toyota");
time.Sleep(1 * time.Second)
}
[ผลลัพธ์จากการ Run]ซื้อแว่น ที่ เซเว่น
ซื้อนาฬิกา ที่ เซ็นทรัล
ซื้อรถ ที่ ศูนย์ Toyota
ซื้อผลไม้ ที่ สยามพารากอน

สังเกตว่าตอนนี้ “ซื้อผลไม้ ที่ สยามพารากอน” นั้นแสดงออกมาแล้ว และอยู่ในลำดับสุดท้าย ทั้งๆที่ ตัว Source Code ของ “ซื้อผลไม้ ที่ สยามพารากอน” อยู่ก่อน “ซื้อรถ ที่ ศูนย์ Toyota”

นั้นเป็นเพราะ จริงๆแล้ว มันทำงานเสร็จใกล้เคียงกัน แต่ด้วย การแสดงผลใน Command Line มันโชว์ผลลัพธ์ซ้อนกันไม่ได้ขนาดนั้น แต่มันเสร็จใกล้เคียงกัน

เพื่อให้เห็นภาพชัดยิ่งขึ้นไปอีก ลองมาดูอีกตัวอย่างนึงครับ

ตัวอย่างที่ 2 จำลองสถานการณ์จริง (สำคัญ)

สถานการณ์คือ เราจะให้ นาย A หรือ Function main() เป็นคนไปซื้อของ 4 อย่าง ใน 4 สถานที่ แตกต่างกัน แต่ละสถานที่ ใช้เวลาในการซื้อ 1 วินาที สรุปเป็นรายการได้แบบนี้

  • นาย A ซื้อแว่นตา ที่เซเว่น 1 วินาที
  • นาย A ซื้อนาฬิกา ที่เซนทรัล 1 วินาที
  • นาย A ซื้อผลไม้ ที่สยามพารากอน 1 วินาที
  • นาย A ซื้อรถยนต์ ที่ศูนย์โตโยต้า 1 วินาที
  • รวมทั้งสิ้นนาย A ใช้เวลาทั้งหมด 4 วินาที

เขียนเป็น Source Code ได้ดังนี้

[Filename main.go]package mainimport (
"fmt"
"time" // Import time มาเพื่อจับเวลาในการ Run
)
func buyGlassesAtSevenEleven() {
time.Sleep(1 * time.Second);
fmt.Println("ซื้อแว่น : ที่เซเว่น : เสร็จแล้ว");
}
func buyWatchAtCentral() {
time.Sleep(1 * time.Second);
fmt.Println("ซื้อนาฬิกา : ที่เซ็นทรัล : เสร็จแล้ว");
}
func buyFruitAtSiamParagon() {
time.Sleep(1 * time.Second);
fmt.Println("ซื้อผลไม้ : ที่สยามพารากอน : เสร็จแล้ว");
}
func buyCarAtToyota() {
time.Sleep(1 * time.Second);
fmt.Println("ซื้อรถ : ที่ศูนย์โตโยต้า : เสร็จแล้ว");
}
func main() {
start := time.Now() // เริ่มจับเวลาในการ Run
buyGlassesAtSevenEleven();
buyWatchAtCentral();
buyFruitAtSiamParagon();
buyCarAtToyota();
fmt.Println("ใช้เวลาในการ Run ทั้งสิ้น : ", time.Since(start), " วินาที") // แสดงเวลาที่ Run ทั้งหมด
}
[ผลลัพธ์จากการ Run]ซื้อแว่น : ที่เซเว่น : เสร็จแล้ว
ซื้อนาฬิกา : ที่เซ็นทรัล : เสร็จแล้ว
ซื้อผลไม้ : ที่สยามพารากอน : เสร็จแล้ว
ซื้อรถ : ที่ศูนย์โตโยต้า : เสร็จแล้วใช้เวลาในการ Run ทั้งสิ้น : 4s วินาที
ทั้งหมดถ้า Run แล้ว นาย A จะใช้เวลา 4 วินาที

ซึ่งการ Run จะได้ 4 วินาที พอดี คราวนี้เราต้องการให้มันเร็วขึ้นอีก 1 เท่า ด้วยการใช้ Go Routine หรือ สร้าง นาย B ขึ้นมา ช่วย นาย A ไปซื้อของ โดยมีรายการแบบนี้

  • นาย ฺB ซื้อแว่นตา ที่เซเว่น 1 วินาที
  • นาย B ซื้อนาฬิกา ที่เซนทรัล 1 วินาที
  • นาย A ซื้อผลไม้ ที่สยามพารากอน 1 วินาที
  • นาย A ซื้อรถยนต์ ที่ศูนย์โตโยต้า 1 วินาที
  • รวมทั้งสิ้น นาย A และ นาย ฺB ใช้เวลาทั้งหมดร่วมกัน 2 วินาที

ดังนั้น Source Code จะเป็นดังนี้

[Filename main.go]package mainimport (
"fmt"
"time" // Import time มาเพื่อจับเวลาในการ Run
)
func buyGlassesAtSevenEleven() {
time.Sleep(1 * time.Second);
fmt.Println("ซื้อแว่น : ที่เซเว่น : เสร็จแล้ว");
}
func buyWatchAtCentral() {
time.Sleep(1 * time.Second);
fmt.Println("ซื้อนาฬิกา : ที่เซ็นทรัล : เสร็จแล้ว");
}
func buyFruitAtSiamParagon() {
time.Sleep(1 * time.Second);
fmt.Println("ซื้อผลไม้ : ที่สยามพารากอน : เสร็จแล้ว");
}
func buyCarAtToyota() {
time.Sleep(1 * time.Second);
fmt.Println("ซื้อรถ : ที่ศูนย์โตโยต้า : เสร็จแล้ว");
}
func main() {
start := time.Now() // เริ่มจับเวลาในการ Run
go buyGlassesAtSevenEleven(); // ใส่ go เพื่อสร้าง นาย B
go buyWatchAtCentral(); // ใส่ go เพื่อสร้าง นาย B
buyFruitAtSiamParagon();
buyCarAtToyota();
fmt.Println("ใช้เวลาในการ Run ทั้งสิ้น : ", time.Since(start), " วินาที") // แสดงเวลาที่ Run ทั้งหมด
}
[ผลลัพธ์จากการ Run]ซื้อแว่น : ที่เซเว่น : เสร็จแล้ว
ซื้อผลไม้ : ที่สยามพารากอน : เสร็จแล้ว
ซื้อนาฬิกา : ที่เซ็นทรัล : เสร็จแล้ว
ซื้อรถ : ที่ศูนย์โตโยต้า : เสร็จแล้ว
ใช้เวลาในการ Run ทั้งสิ้น : 2s วินาที
เมื่อนาย A และ นาย B ช่วยกันทำแล้ว มันจะเร็วขึ้น อย่างเห็นได้ชัด

คืออย่างที่บอก นาย B ก็จะไปซื้อ ของ 2 อย่างแทนนาย A ทุกอย่างมันก็เลยเหมือนมีคน 2 คนมาช่วยกันทำงาน ทำให้ งานเสร็จเร็วขึ้น 2 เท่า

โอเคครับนี่ก็คือเรื่องราวของ Go Routine สำหรับบทความนี้ แต่มันยังไม่จบเพียงเท่านี้ เพราะ จริงๆ แล้ว Go Routine มันมีเรื่องที่ต้องรู้อีก 1 เรื่อง

คือเรื่องของ Channel ซึ่งเป็นเรื่องที่ค่อนข้างสำคัญในเรื่องของ Go Routine ดังนั้น ในบทความต่อไป เราจะมา สอนใช้ Channel ร่วมกับ Go Routine

เอ๊ะ ว่ามันใช้ร่วมกันยังไงนะ และ Channel คืออะไรใช้ยังไง เจอกันใน ตอนที่ 6 ครับ

--

--