1.1 ภาพรวมของช่อง
ช่องเป็นคุณลักษณะที่สำคัญมากใน Go ภาษา ใช้สำหรับการสื่อสารระหว่างกอรูทีนท์ที่แตกต่างกัน Go ภาษาโมเดลของการพร้อมกันเป็น CSP (Communicating Sequential Processes) ซึ่งในนั้น ช่องเล่นบทบาทของการส่งข้อความ การใช้ช่องสามารถหลีกเลี่ยงการแบ่งปันหน่วยความจำที่ซับซ้อน ทำให้การออกแบบโปรแกรมพร้อมกันเป็นเรื่องที่ง่ายและปลอดภัยขึ้น
1.2 สร้างและปิดช่อง
ใน Go ภาษา ช่องถูกสร้างโดยใช้ฟังก์ชัน make
ซึ่งสามารถระบุประเภทและขนาดของบัฟเฟอร์ของช่องได้ ขนาดของบัฟเฟอร์เป็นอ็อฉเช่นไม่ระบุขนาดจะสร้างช่องที่ไม่มีบัฟเฟอร์
ch := make(chan int) // สร้างช่องที่ไม่มีบัฟเฟอร์ของประเภท int
chBuffered := make(chan int, 10) // สร้างช่องที่มีบัฟเฟอร์ที่มีความจุ 10 สำหรับประเภท int
การปิดช่องอย่างถูกต้องก็มีความสำคัญ เมื่อข้อมูลไม่ได้รับการส่งต่อแล้ว ควรปิดช่องเพื่อหลีกเลี่ยงสภาวะที่อาจเกิดอัตตาธิการหรือสภาวะที่กอรูทีน์อื่น ๆ กำลังรอข้อมูลอยู่อย่างไม่มีที่สิ้นสุด
close(ch) // ปิดช่อง
1.3 การส่งและรับข้อมูล
การส่งและรับข้อมูลในช่องง่าย โดยใช้สัญลักษณ์ <-
การดำเนินการส่งอยู่ทางซ้ายและการดำเนินการรับอยู่ทางขวา
ch <- 3 // ส่งข้อมูลไปยังช่อง
value := <- ch // รับข้อมูลจากช่อง
อย่างไรก็ตาม สิ่งสำคัญที่จะทราบคือ การดำเนินการส่งจะบล็อกไปจนกว่าข้อมูลจะถูกรับ และการดำเนินการรับก็จะบล็อกไปเมื่อมีข้อมูลที่จะอ่าน
fmt.Println(<-ch) // นี่
จะบล็อกจนกว่าจะมีข้อมูลถูกส่งมาจาก ch
2 Advanced Usage of Channels
2.1 ความจุและการบัฟเฟอร์ของช่อง
ช่องสามารถถูกบัฟเฟอร์หรือไม่ได้บัฟเฟอร์ ช่องที่ไม่ได้บัฟเฟอร์จะบล็อกผู้ส่งจนกว่าผู้รับจะพร้อมที่จะรับข้อความ ช่องที่ไม่ได้บัฟเฟอร์ทรัพยากรการส่งและรับที่สื่อสารยังขั้นตอนบางอย่าง
ch := make(chan int) // สร้างช่องที่ไม่ได้บัฟเฟอร์
go func() {
ch <- 1 // นี้ จะบล็อกหากไม่มีกอรูทีน์ที่พร้อมที่จะรับ
}()
ช่องที่บัฟเฟอร์มีที่จำกัด และการส่งข้อมูลไปยังช่องจะบล็อกเมื่อบัฟเฟอร์เต็ม โดยเดียวกัน การพยายามในการรับจากบัฟเฟอร์ที่ว่างเป็นอัตโนมัติต้องบล็อก ช่องที่บัฟเฟอร์นั้นท通ทางส่วนมากจะใช้สำหรับการจัดการการสื่อสารที่มีการส่งเข้ามาด้วยประมาณสูงและสถานการณ์การสื่อสารแบบไม่สม่ำเสมอ เพื่อลดการสูญเสียทรัพยากรที่เกิดจากการรออย่างตรงไปตรงมา
ch := make(chan int, 10) // สร้างช่องที่บัฟเฟอร์มีความจุ 10
go func() {
for i := 0; i < 10; i++ {
ch <- i // นี่จะไม่บล็อกไม่ว่าก็ต้องการช่องเต็มแล้ว
}
close(ch) // ปิดช่องหลังจากการส่งเสร็จสิ้น
}()
การเลือกประเภทของช่องขึ้นอยู่กับลักษณะของการสื่อสาร ว่าจำเป็นต้องระดับการสะท้อนหรือไม่ ว่าจำเป็นต้องมีการบัฟเฟอร์หรือไม่ และความต้องการทางการกระทำ
2.2 การใช้คำสั่ง select
เมื่อการเลือกระหว่างช่องหลาย ๆ ใช้คำสั่ง select
มีประโยชน์มาก คล้ายกับคำสั่งสวิตช์ แต่ทุกเคสภายในมีการดำเนินการช่อง มันสามารถฟังการลหาข้อมูลในช่องและเมื่อมีการที่หลายช่องพร้อมพร้อมกัน select
จะสุ่มเลือกให้ทำงานอย่างไรก็ตาม
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch1 <- i
}
}()
go func() {
for i := 0; i < 5; i++ {
ch2 <- i * 10
}
}()
for i := 0; i < 5; i++ {
select {
case v1 := <-ch1:
fmt.Println("ได้รับจาก ch1:", v1)
case v2 := <-ch2:
fmt.Println("ได้รับจาก ch2:", v2)
}
}
การใช้ select
สามารถจัดการสถานการณ์สื่อสารที่ซับซ้อน เช่น การรับข้อมูลจากหลายช่องพร้อมกันหรือการส่งข้อมูลโดยแบบเงื่อนไขเฉพาะ ๆ
2.3 การวนลูปด้วย Range สำหรับช่องข้อมูล (Channels)
การใช้คำสำคัญ range
เพื่อรับข้อมูลจากช่องข้อมูล (channel) อย่างต่อเนื่องจนกว่าช่องข้อมูลจะถูกปิด นี่เป็นการใช้งานที่มีประโยชน์มากเมื่อต้องจัดการกับปริมาณของข้อมูลที่ไม่รู้ล่วงหน้า โดยเฉพาะในรูปแบบโมเดลผลิต-ผู้บริโภค
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch) // จำไว้ว่าต้องปิดช่องข้อมูล
}()
for n := range ch {
fmt.Println("Received:", n)
}
เมื่อช่องข้อมูลถูกปิดและไม่มีข้อมูลที่เหลืออีกต่อไป ลูปจะสิ้นสุด หากลืมที่จะปิดช่องข้อมูล range
จะสร้างการรั่วกระจาย goroutine และโปรแกรมอาจรอคอยอย่างไม่มีที่สิ้นสุดเพื่อรอข้อมูลมาถึงจนกว่าอยู่ในช่องข้อมูล
3 การจัดการสถานการณ์ที่ซับซ้อนในการทำงานแบบเส้นโครงเวียน
3.1 บทบาทของ Context
ในการเขียนโปรแกรมแบบเส้นโครงเวียนของ Go language แพ็คเกจ context
เป็นสิ่งสำคัญ มันถูกใช้เพื่อทำให้การจัดการข้อมูล สัญญาการยกเลิก ข้อกำหนดเวลา ฯลฯ ระหว่าง goroutines ต่าง ๆ ที่จัดการดิสพิพัศษเช่นต่าง ๆ ในโดเมนของคำขอเดียว
สมมติว่าเซอร์วิสเว็บต้องการคิวรีฐานข้อมูลและทำการคำนวณบางอย่างบนข้อมูล ซึ่งจำเป็นต้องทำงานข้าม goroutines หลาย ๆ ตัว หากผู้ใช้ยกเลิกคำขออย่างนึงอย่างไม่คาดคิดหรือเซอร์วิสต้องทำการเสร็จคำขอภายในเวลาที่กำหนด เราจำเป็นต้องมีกลไกในการยกเลิก goroutines ที่กำลังทำงานอยู่
ที่นี่เราใช้ context
เพื่อบรรลุความต้องการดังกล่าว:
package main
import (
"context"
"fmt"
"time"
)
func operation1(ctx context.Context) {
time.Sleep(1 * time.Second)
select {
case <-ctx.Done():
fmt.Println("operation1 ถูกยกเลิก")
return
default:
fmt.Println("operation1 เสร็จสิ้น")
}
}
func operation2(ctx context.Context) {
time.Sleep(2 * time.Second)
select {
case <-ctx.Done():
fmt.Println("operation2 ถูกยกเลิก")
return
default:
fmt.Println("operation2 เสร็จสิ้น")
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
go operation1(ctx)
go operation2(ctx)
<-ctx.Done()
fmt.Println("main: context ถูกทำสำเร็จ")
}
ในโค้ดด้านบน context.WithTimeout
ถูกใช้เพื่อสร้าง Context ที่ยกเลิกโดยอัตโนมัติหลังจากเวลาที่กำหนด ฟังก์ชัน operation1
และ operation2
มี select
บล็อกที่รอการ ctx.Done()
ทำให้งี้หยุดทันทีเมื่อ Context ส่งสัญญาณยกเลิก
3.2 การจัดการข้อผิดพลาดด้วยช่องข้อมูล (Channels)
เมื่อเข้าถึงการเขียนโปรแกรมแบบเส้นโครงเวียน การจัดการข้อผิดพลาด เป็นปัจจัยที่สำคัญที่จะต้องพิจารณา ใน Go คุณสามารถใช้ช่องข้อมูลร่วมกับ goroutines เพื่อจัดการข้อผิดพลาดโดยไม่เป็นพรรคหรืออย่างประมาณ
ตัวอย่างโค้ดต่อไปนี้จะสาธิตวิธีการส่งข้อผิดพลาดออกจาก goroutine และจัดการกับมันใน goroutine หลัก:
package main
import (
"errors"
"fmt"
"time"
)
func performTask(id int, errCh chan<- error) {
// จำลองงานที่อาจจะประสบความสำเร็จหรือความล้มเหลวโดยบังเหียนอย่างสุ่ม
if id%2 == 0 {
time.Sleep(2 * time.Second)
errCh <- errors.New("งานล้มเหลว")
} else {
fmt.Printf("งาน %d ทำเสร็จสบบร้อย\n", id)
errCh <- nil
}
}
func main() {
tasks := 5
errCh := make(chan error, tasks)
for i := 0; i < tasks; i++ {
go performTask(i, errCh)
}
for i := 0; i < tasks; i++ {
err := <-errCh
if err != nil {
fmt.Printf("ได้รับข้อผิดพลาด: %s\n", err)
}
}
fmt.Println("ทำการดำเนินการงานทั้งหมดเสร็จสิ้น")
}
ในตัวอย่างนี้เรากำหนดฟังก์ชัน performTask
เพื่อจำลองงานที่อาจจะสำเร็จหรือล้มเหลว ข้อผิดพลาดถูกส่งกลับไปยัง goroutine หลักผ่านช่องข้อมูล errCh
ซึ่งถูกสร้างขึ้นด้วยจำกัดการส่ง ความน้อยเราทำให้รันโปรแกรมไปไม่ชะงัยเพราะกับข้อผิดพลาดที่ยังไม่ได้รับการรับไว้
เทคนิคเหล่านี้เป็นเครื่องมือที่มีกำลังสำหรับการจัดการกับสถานการณ์ที่ซับซ้อนในการทำงานแบบเส้นโครงเวีน การใช้งานตรงและถูกต้องจะทำให้โค้ดมีความทนทานมากขึ้น อ่านและรักษมุดดดูรักษาได้ดีขึ้น