UNDER CONSTRUCTION !

This is not the final version of the site

UNDER CONSTRUCTION !

This is not the final version of the site

UNDER CONSTRUCTION !

This is not the final version of the site

เขียน Goroutine อย่างง่าย

Feb 21, 2025

วันนี้ผมจะมาสอนเขียน Goroutine แบบง่าย ๆ กัน

เริ่มแรกเลยคือ เราต้องมาทำความรู้จักกับ Goroutine กันก่อน

Goroutine คืออะไร

Goroutine ใน Golang คือ lightweight thread ที่ทำให้โค้ดสามารถทำงานแบบ concurrent ได้

การสร้าง Goroutine สามารถทำได้โดยใช้ keyword "go" หน้า function ที่ต้องการให้ทำงานแบบ concurrent


ตัวอย่างเช่น

package main

import (
	"fmt"
	"time"
)

func sayHello() {
	fmt.Println("Hello, Goroutine!")
}

func main() {
	sayHello()

	fmt.Println("Hello, Main!")
}


เป็นโปรแกรมง่ายๆ ที่ print "Hello, Goroutine!" จากฟังก์ชั่น sayHello

และ print "Hello, Main!" จาก main


ผลลัพธ์ที่ได้ แน่นอนว่าจะออกมาเป็น

Hello, Goroutine!
Hello, Main

เพราะมีการเรียก sayHello ก่อน


ทีนี่ถ้าเราลองปรับด้วยการเรียก sayHello ด้วย keyword go เพื่อเรียก sayHello ด้วย Goroutine ใหม่

package main

import (
	"fmt"
	"time"
)

func sayHello() {
	fmt.Println("Hello, Goroutine!")
}

func main() {
	go sayHello()

}


จะได้ผลลัพธ์เป็น

Hello, Main

สังเกตุว่าจะไม่เห็นไม่มี "Hello, Goroutine!" แสดงออกมา

นั่นก็เป็นเพราะ sayHello ทำงานที่อีก Goroutine นึง ทำให้ main function ไม่ได้รอให้ sayHello print ก่อน จึง exit program


วิธีแก้ปัญหาแบบง่ายๆ คือ เพิ่ม time.Sleep เพื่อให้ main function รอ sayHello ทำงานเสร็จก่อน

package main

import (
	"fmt"
	"time"
)

func sayHello() {
	fmt.Println("Hello, Goroutine!")
}

func main() {
	go sayHello()

	time.Sleep(1 * time.Second)
}

ผลลัพธ์จากการรันโปรแกรมจะได้

Hello, Main!
Hello, Goroutine



แต่อย่างไรก็ตามวิธีการใช้ time.Sleep จะใช้ได้ผลก็ต่อเมื่อเราแน่ใจได้ว่า sayHello นั่นทำงานเสร็จทัน 1 second ตามที่เรา time.Sleep รอเอาไว้


ลองยกตัวอย่างการทำงานของ sayHello ที่ทำงานนานกว่า 1 second


package main

import (
	"fmt"
	"time"
)

func sayHello() {
	time.Sleep(2 * time.Second)
	fmt.Println("Hello, Goroutine!")
}

func main() {
	go sayHello()

	fmt.Println("Hello, Main!")

	time.Sleep(1 * time.Second)

}


แน่นอน ว่าจะได้ผลลัพธ์เป็น

Hello, Main

เพราะ main function นั้นเสร็จก่อน sayHello ทำงานเสร็จ


อีกวิธีแก้คือใช้ sync.WaitGroup ในการส่งสัญญานบอกการทำงานเสร็จของ Goroutine


ตัวอย่างเช่น

package main

import (
	"fmt"
	"sync"
)

func sayHello(wg *sync.WaitGroup) {
	defer wg.Done()
	fmt.Println("Hello, Goroutine!")
}

func main() {
	var wg sync.WaitGroup

	wg.Add(1)

	go sayHello(&wg)

	fmt.Println("Hello, Main!")

	wg.Wait()

}


ในตัวอย่างนี้เรามีการประกาศ

var wg sync.WaitGroup

เพื่อใช้ในการส่งสัญญานการทำงานเสร็จของ Goroutine


มีการเรียก

wg.Add(1)

คือเป็นการเพิ่ม counter ให้ WaitGroup เพื่อบอกว่ามี Goroutine ที่ต้องรอ (Wait) จำนวน 1 ตัว


หลังจากนั้นเราก็ส่ง &wg เข้าไปยัง Goroutine sayHello

go sayHello(&wg)


ในส่วนของ sayHello function เราก็มีการเรียก

defer wg.Done()


เพื่อเป็นการสงสัญญาณบอก WaitGroup ว่า Goroutine ตัวนั้นทำงานเสร็จแล้ว โดยเมื่อเรียก Done ตัว Counter จะลดไป 1

การใช้ defer เพื่อเป็นการ execute หลัง function ทำงานเสร็จ


กลับมาที่ main function

fmt.Println("Hello, Main!")

wg.Wait()

เรามีการเรียด Wait หลัง print เพื่อเป็นการรอให้ Couter ของ WaitGroup เป็น 0 ก่อนทำงานต่อ นั่นคือการรอให้ Goroutine ทำงานเสร็จก่อนนั่นเอง


ผลลัพธ์ที่ได้คือ

Hello, Main!
Hello, Goroutine


โดยที่ไม่ต้องใช้ time.Sleep และไม่ว่า Goroutine จะทำงานนานแค่ไหน ก็เป็นการการันตีว่า Main function จะรอเสมอ