DEV Community

Mossie Chao
Mossie Chao

Posted on

Go routines

Go routines

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

การใช้งาน Goroutine ในภาษา Go อนุญาติให้ทำงานหลายๆงานในเวลาเดียวกัน (coroutine) งานเหล่าๆนั้น (routine) สามารถทำงานได้ใน process เดียวกัน Goroutine นั้นไม่แบ่งพื้นที่กันกับคนอื่นทำให้มันแตกต่างจาก thread แต่อย่างไรก็ตาม เราสามารถส่งตัวแปรเข้าไปอีก coroutine ได้ในโค็ด แต่อาจจะทำให้มันเกิดการทำงานที่คาดเดาไม่ได้

ลองมาดูการทำงานของ ฟังก์ชัน hello

func hello() {
    fmt.Println("hello world")
}
Enter fullscreen mode Exit fullscreen mode

ทีนี้เราจะลองเรียกใช้ฟังก์ชันนี้ในแบบ coroutine

func main() {
    fmt.Println("hello")

    // รูปแบบการเรียกใช้ฟังก์ชัน hello แบบ coroutine
    go hello()

    fmt.Println("end")
}
Enter fullscreen mode Exit fullscreen mode

จะเห็นผลลัพท์คือไม่มีการแสดงข้อความ hello world ออกมา ว่าฟังก์ชัน main ไม่สนว่า ฟังก์ชัน hello นั้นจะทำงานนานแค่ไหน พอมันเรียกใช้งานฟังก์ชัน hello แล้ว ฟังก์ชัน main ก็ทำงานต่อเลย


มาลองใช้ Concurrent Routines

ลองจิตนาการว่าเราจะคำนวนค่าต่างๆ 2 ครั้ง ครั้งแรกเราจะหาผลรวมตัวเลขจาก 1 - 10 หลังจากนั้น หาผลรวมตัวเลขของ 1 - 100 เพื่อไม่ให้เป็นการเสียเวลาเราจะทำการคำนวนของค่านี้ไปพร้อมๆกันด้วย coroutine

func main() {
    var s1 int
    go func() {
        s1 = sum(1, 100)
    }()

    log.Println(s1)
}

func sum(from, to int) int {
    res := 0
    for i := from; i <= to; i++ {
        res += i
    }

    return res
}
Enter fullscreen mode Exit fullscreen mode

ตัวแปร s1 จะแสดงมาเป็น 0 เนื่องจากในตอนเรียกฟังก์ชัน sum ใน main ยังทำงานไม่เสร็จและตัวแปร s1 ยังคงเป็น 0

ทีนี้ลองเรียกเป็นแบบ

func main() {
    var s1 int
    go func() {
        s1 = sum(1, 100)
    }()

    // ลองเพิ่ม statement ไปเพื่อรอให้ฟังก์ชัน sum ทำงานเสร็จ
    time.Sleep(time.Second)

    log.Println(s1)
}

func sum(from, to int) int {
    res := 0
    for i := from; i <= to; i++ {
        res += i
    }

    return res
}
Enter fullscreen mode Exit fullscreen mode

WaitGroup

หากเราทำงาน coroutine มากกว่า 1 ตัวขึ้นไป เพื่อที่จะควบคุมการทำงานของ coroutine เป็นไปตามลำดับที่เราต้องการ เราต้องใช้ WaitGroup{} ช่วย

package main 
import "sync" 
func main() {
    // สร้าง pointer ชี้ไปที่ wait group
  wg := &sync.WaitGroup{}

    // เพิ่มคำสั่ง asynchronous เข้าไป เพื่อบอกให้รู้ว่าให้รอ goroutine 1 ตัวด้านล่างทำงานให้เสร็จก่อนไปทำงานอื่น
  wg.Add(1)
  go func() {
        // wait routine
        ...
        .
        .
        wg.Done() // บอกตัว wait group ว่าใน go routine นี้ทำงานเสร็จแล้ว
    }()
  wg.Wait()

    // หลังจากทำงาน go routine ด้านบนเสร็จแล้วก็ทำอันด้านล่างต่อ
  ………….
  ………….
}
Enter fullscreen mode Exit fullscreen mode

หลังจากที่เราได้ปรับใช้ wait group กับการคำนวน ผลรวมตั้งแต่ 1 - 10 และ 1 - 100

package main

import (
    "log"
    "sync"
)

func main() {
    s1 := 0
    s2 := 0
    wg := &sync.WaitGroup{}

    // ถ้าเราใส่แค่ 1 จะเกิด panic ทันทีเพราะ wg ถูกใช้งาน 2 ครั้ง
    wg.Add(2)
    go sum(1, 100, wg, &s1)
    go sum(1, 200, wg, &s2)
    wg.Wait()

    log.Println(s1, s2)
}

func sum(from, to int, wg *sync.WaitGroup, res *int) {
    *res = 0
    for i := from; i <= to; i++ {
        *res += i
    }

    wg.Done()
}
Enter fullscreen mode Exit fullscreen mode

... WIP ...

Top comments (0)