รู้เบื้องต้นเกี่ยวกับการทำงานพร้อมกันใน Go
การทำงานพร้อมกันคือการจัดระเบียบของงานอิสระที่ดำเนินการโดยโปรแกรมในลักษณะพร้อมกันหรือแบบขนานเทียม การทำงานพร้อมกันเป็นลักษณะพื้นฐานของการเขียนโปรแกรมสมัยใหม่ ช่วยให้นักพัฒนาสามารถใช้ประโยชน์จากศักยภาพของโปรเซสเซอร์แบบมัลติคอร์ได้อย่างเต็มที่ จัดการทรัพยากรระบบได้อย่างมีประสิทธิภาพ และลดความซับซ้อนของการออกแบบแอปพลิเคชันที่ซับซ้อน
Go หรือที่เรียกว่า golang เป็นภาษาโปรแกรมคอมไพล์แบบสแตติกที่ออกแบบโดยคำนึงถึงความเรียบง่ายและมีประสิทธิภาพเป็นหลัก รูปแบบการทำงานพร้อมกันได้รับแรงบันดาลใจจาก Communicating Sequential Processes (CSP) ของ Tony Hoare ซึ่งเป็นรูปแบบที่ส่งเสริมการสร้างกระบวนการอิสระที่เชื่อมต่อกันด้วยช่องทางการส่งข้อความที่ชัดเจน การทำงานพร้อมกันใน Go เกี่ยวข้องกับแนวคิดของ goroutines, channel และคำสั่ง 'select'
ฟีเจอร์หลักเหล่านี้ช่วยให้นักพัฒนาสามารถเขียนโปรแกรมที่ทำงานพร้อมกันในระดับสูงได้อย่างง่ายดายและใช้โค้ดสำเร็จรูปน้อยที่สุด ขณะเดียวกันก็รับประกันการสื่อสารและการซิงโครไนซ์ที่ปลอดภัยและแม่นยำระหว่างงานต่างๆ ที่ AppMaster นักพัฒนาสามารถควบคุมพลังของโมเดลการทำงานพร้อมกันของ Go เพื่อสร้างแอปพลิเคชันแบ็กเอนด์ที่ปรับขนาดได้และมีประสิทธิภาพสูงด้วยตัวออกแบบพิมพ์เขียวแบบภาพและการสร้างซอร์สโค้ดอัตโนมัติ
Goroutines: หน่วยการสร้างของการทำงานพร้อมกัน
ใน Go การทำงานพร้อมกันถูกสร้างขึ้นจากแนวคิดของ goroutines ซึ่งเป็นโครงสร้างคล้ายเธรดที่มีน้ำหนักเบาซึ่งจัดการโดยตัวกำหนดตารางเวลารันไทม์ของ Go Goroutines มีราคาถูกอย่างไม่น่าเชื่อเมื่อเทียบกับ OS thread และนักพัฒนาสามารถวางไข่ได้หลายพันหรือหลายล้านรายการในโปรแกรมเดียวโดยไม่ต้องใช้ทรัพยากรระบบมากเกินไป หากต้องการสร้าง goroutine เพียงนำหน้าการเรียกใช้ฟังก์ชันด้วยคีย์เวิร์ด 'go' เมื่อเรียกใช้ ฟังก์ชันจะทำงานพร้อมกันกับส่วนที่เหลือของโปรแกรม:
func printMessage(message string) { fmt.Println(message) } func main() { go printMessage("Hello, concurrency!") fmt.Println("This might print first.") }
โปรดสังเกตว่าลำดับของข้อความที่พิมพ์ไม่ได้ถูกกำหนดขึ้น และข้อความที่สองอาจถูกพิมพ์ก่อนข้อความแรก สิ่งนี้แสดงให้เห็นว่า goroutines ทำงานพร้อมกันกับส่วนที่เหลือของโปรแกรม และไม่รับประกันคำสั่งการดำเนินการของ goroutine ตัวกำหนดตารางเวลารันไทม์ Go มีหน้าที่จัดการและดำเนินการ goroutines เพื่อให้แน่ใจว่ารันไทม์พร้อมกันในขณะที่เพิ่มประสิทธิภาพการใช้งาน CPU และหลีกเลี่ยงการสลับบริบทที่ไม่จำเป็น ตัวกำหนดตารางเวลาของ Go ใช้อัลกอริธึมการขโมยงานและกำหนดเวลา goroutines แบบร่วมมือ เพื่อให้มั่นใจว่าจะให้การควบคุมเมื่อเหมาะสม เช่น ในระหว่างการดำเนินการที่ยาวนานหรือเมื่อรอเหตุการณ์เครือข่าย
โปรดทราบว่า goroutines แม้จะมีประสิทธิภาพ แต่ก็ไม่ควรใช้อย่างไม่ระมัดระวัง สิ่งสำคัญคือต้องติดตามและจัดการวงจรชีวิตของ goroutines ของคุณเพื่อให้แน่ใจว่าแอปพลิเคชันมีความเสถียรและหลีกเลี่ยงการรั่วไหลของทรัพยากร นักพัฒนาควรพิจารณาใช้รูปแบบ เช่น พูลผู้ปฏิบัติงาน เพื่อจำกัดจำนวนของ goroutines ที่แอ็คทีฟ ณ เวลาใดก็ตาม
ช่องทาง: การซิงโครไนซ์และการสื่อสารระหว่าง Goroutines
แชนเนลเป็นส่วนพื้นฐานของโมเดลการทำงานพร้อมกันของ Go ซึ่งช่วยให้ goroutines สามารถสื่อสารและซิงโครไนซ์การดำเนินการได้อย่างปลอดภัย ช่องเป็นค่าชั้นหนึ่งใน Go และสามารถสร้างได้โดยใช้ฟังก์ชัน 'make' โดยมีขนาดบัฟเฟอร์เสริมเพื่อควบคุมความจุ:
// Unbuffered channel ch := make(chan int) // Buffered channel with a capacity of 5 bufCh := make(chan int, 5)
การใช้แชนเนลบัฟเฟอร์ที่มีความจุที่ระบุทำให้สามารถเก็บค่าหลายค่าในแชนเนลได้ โดยทำหน้าที่เป็นคิวอย่างง่าย สิ่งนี้สามารถช่วยเพิ่มปริมาณงานได้ในบางสถานการณ์ แต่นักพัฒนาจะต้องระมัดระวังไม่ให้เกิดปัญหาการหยุดชะงักหรือปัญหาการซิงโครไนซ์อื่นๆ การส่งค่าผ่านช่องสัญญาณจะดำเนินการผ่านตัวดำเนินการ '<-':
// Sending the value 42 through the channel ch <- 42 // Sending values in a for loop for i := 0; i < 10; i++ { ch <- i }
ในทำนองเดียวกัน การรับค่าจากแชนเนลจะใช้ตัวดำเนินการ '<-' ตัวเดียวกัน แต่ใช้แชนเนลทางขวามือ:
// Receiving a value from the channel value := <-ch // Receiving values in a for loop for i := 0; i < 10; i++ { value := <-ch fmt.Println(value) }
แชนเนลนำเสนอสิ่งที่เป็นนามธรรมที่เรียบง่ายแต่ทรงพลังสำหรับการสื่อสารและซิงโครไนซ์ goroutines เมื่อใช้แชนเนล นักพัฒนาสามารถหลีกเลี่ยงหลุมพรางทั่วไปของโมเดลหน่วยความจำที่ใช้ร่วมกัน และลดโอกาสของการแย่งชิงข้อมูลและปัญหาการเขียนโปรแกรมพร้อมกันอื่นๆ เพื่อเป็นตัวอย่าง ให้พิจารณาตัวอย่างต่อไปนี้ซึ่งฟังก์ชันที่ทำงานพร้อมกันสองฟังก์ชันจะรวมองค์ประกอบของสองส่วนและเก็บผลลัพธ์ไว้ในตัวแปรที่ใช้ร่วมกัน:
func sumSlice(slice []int, result *int) { sum := 0 for _, value := range slice { sum += value } *result = sum } func main() { slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{6, 7, 8, 9, 10} sharedResult := 0 go sumSlice(slice1, &sharedResult) go sumSlice(slice2, &sharedResult) time.Sleep(1 * time.Second) fmt.Println("Result:", sharedResult) }
ตัวอย่างข้างต้นมีแนวโน้มที่จะเกิดการแย่งชิงข้อมูล เนื่องจาก goroutine ทั้งสองเขียนไปยังตำแหน่งหน่วยความจำที่ใช้ร่วมกันเดียวกัน ด้วยการใช้ช่องทาง การสื่อสารจะปลอดภัยและปราศจากปัญหาดังกล่าว:
func sumSlice(slice []int, ch chan int) { sum := 0 for _, value := range slice { sum += value } ch <- sum } func main() { slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{6, 7, 8, 9, 10} ch := make(chan int) go sumSlice(slice1, ch) go sumSlice(slice2, ch) result1 := <-ch result2 := <-ch fmt.Println("Result:", result1 + result2) }
ด้วยการใช้คุณสมบัติการทำงานพร้อมกันในตัวของ Go นักพัฒนาสามารถสร้างแอปพลิเคชันที่ทรงพลังและปรับขนาดได้อย่างง่ายดาย ด้วยการใช้ goroutines และช่องสัญญาณ พวกเขาสามารถควบคุมศักยภาพของฮาร์ดแวร์สมัยใหม่ได้อย่างเต็มที่ ในขณะที่รักษารหัสที่ปลอดภัยและสวยงาม ที่ AppMaster ภาษา Go ช่วยให้นักพัฒนาสามารถสร้าง แอปพลิเคชันแบ็กเอนด์ ด้วยภาพ โดยเสริมด้วยการสร้างซอร์สโค้ดอัตโนมัติเพื่อประสิทธิภาพและความสามารถในการปรับขนาดระดับสูงสุด
รูปแบบการทำงานพร้อมกันทั่วไปใน Go
รูปแบบการทำงานพร้อมกันเป็นวิธีแก้ปัญหาที่ใช้ซ้ำได้สำหรับปัญหาทั่วไปที่เกิดขึ้นขณะออกแบบและใช้งานซอฟต์แวร์พร้อมกัน ในส่วนนี้ เราจะสำรวจรูปแบบการทำงานพร้อมกันที่เป็นที่นิยมมากที่สุดใน Go รวมถึง fan-in/fan-out, worker pools, ไปป์ไลน์ และอื่นๆ
พัดลมเข้า/พัดลมออก
รูปแบบ fan-in/fan-out จะใช้เมื่อคุณมีหลายงานที่สร้างข้อมูล (fan-out) จากนั้นมีงานเดียวที่ใช้ข้อมูลจากงานเหล่านั้น (fan-in) ใน Go คุณสามารถใช้รูปแบบนี้โดยใช้ goroutines และช่อง ส่วนกระจายออกถูกสร้างขึ้นโดยการเปิดใช้ goroutine หลายรายการเพื่อสร้างข้อมูล และส่วนกระจายเข้าถูกสร้างขึ้นโดยการใช้ข้อมูลโดยใช้ช่องสัญญาณเดียว ``` go func FanIn(ช่อง ...<-chan int) <-chan int { var wg sync.WaitGroup out := make(chan int) wg.Add(len(channels)) for _, c := range ช่อง { go func(ch <-chan int) { for n := range ch { out <- n } wg.Done() }(c) } go func() { wg.Wait() close(out) }( ) กลับออกไป } ```
พูลคนงาน
พูลผู้ปฏิบัติงานคือชุดของ goroutines ที่ดำเนินงานเดียวกันพร้อมกัน โดยกระจายภาระงานระหว่างกัน รูปแบบนี้ใช้เพื่อจำกัดการทำงานพร้อมกัน จัดการทรัพยากร และควบคุมจำนวน goroutines ที่เรียกใช้งาน ใน Go คุณสามารถสร้างกลุ่มผู้ปฏิบัติงานโดยใช้ goroutines ช่อง และคีย์เวิร์ด 'range' ร่วมกัน ``` go func WorkerPool(worker int, งาน <-chan งาน, ผลลัพธ์ chan<- ผลลัพธ์) { for i := 0; ฉัน < คนงาน; i++ { go func() { สำหรับงาน := range งาน { ผลลัพธ์ <- job.Execute() } }() } } ```
ท่อส่ง
รูปแบบไปป์ไลน์คือห่วงโซ่ของงานที่ประมวลผลข้อมูลตามลำดับ โดยแต่ละงานจะส่งเอาต์พุตไปยังงานถัดไปเป็นอินพุต ใน Go รูปแบบไปป์ไลน์สามารถนำไปใช้ได้โดยใช้ชุดของช่องสัญญาณเพื่อส่งผ่านข้อมูลระหว่าง goroutines โดย goroutine หนึ่งอันทำหน้าที่เป็นสเตจในไปป์ไลน์ ``` go func ไปป์ไลน์ (อินพุต <-chan Data) <-chan ผลลัพธ์ { stage1 := stage1(input) stage2 := stage2(stage1) return stage3(stage2) } ```
การจำกัดอัตรา
การจำกัดอัตราเป็นเทคนิคที่ใช้ในการควบคุมอัตราที่แอปพลิเคชันใช้ทรัพยากรหรือดำเนินการบางอย่าง สิ่งนี้มีประโยชน์ในการจัดการทรัพยากรและป้องกันระบบโอเวอร์โหลด ใน Go คุณสามารถใช้การจำกัดอัตราโดยใช้ time.Ticker และคำสั่ง 'select' ``` go func RateLimiter(requests <-chan Request, rate time.Duration) <-chan Response { limit := time.NewTicker(rate) responses := make(chan Response) go func() { defer close(responses) สำหรับ req := คำขอช่วง { <-limit.C การตอบสนอง <- req.Process() } }() ส่งคืนการตอบสนอง } ```
รูปแบบการยกเลิกและหมดเวลา
ในโปรแกรมที่ทำงานพร้อมกัน อาจมีบางสถานการณ์ที่คุณต้องการยกเลิกการดำเนินการหรือตั้งค่าการหมดเวลาสำหรับการดำเนินการให้เสร็จสิ้น Go นำเสนอแพ็คเกจบริบท ซึ่งช่วยให้คุณจัดการวงจรชีวิตของ goroutine ทำให้สามารถส่งสัญญาณให้ยกเลิก กำหนดเส้นตาย หรือแนบค่าที่จะแบ่งปันผ่านเส้นทางการโทรแยก ``` go func WithTimeout(ctx context.Context, Duration time.Duration, task func() error) ข้อผิดพลาด { ctx, cancel := context.WithTimeout(ctx, Duration) defer cancel() done := make(chan error, 1) go func() { เสร็จสิ้น <- งาน () }() เลือก { กรณี <-ctx.Done(): กลับ ctx.Err() กรณี err := <-done: กลับ err } } ```
การจัดการข้อผิดพลาดและการกู้คืนในโปรแกรมที่ทำงานพร้อมกัน
การจัดการข้อผิดพลาดและการกู้คืนเป็นองค์ประกอบสำคัญของโปรแกรมทำงานพร้อมกันที่ทรงพลัง เนื่องจากช่วยให้โปรแกรมตอบสนองต่อสถานการณ์ที่ไม่คาดคิดและดำเนินการต่อไปในลักษณะที่มีการควบคุม ในส่วนนี้ เราจะพูดถึงวิธีจัดการกับข้อผิดพลาดในโปรแกรม Go ที่ทำงานพร้อมกัน และวิธีกู้คืนจากความตื่นตระหนกใน goroutines
การจัดการข้อผิดพลาดในโปรแกรมที่ทำงานพร้อมกัน
- ส่งข้อผิดพลาดผ่านช่องสัญญาณ : คุณสามารถใช้ช่องเพื่อส่งค่าข้อผิดพลาดระหว่าง goroutines และปล่อยให้ผู้รับจัดการตามนั้น ``` go func worker(งาน <-chan int, ผลลัพธ์ chan<- int, errs chan<- error) { สำหรับงาน := งานช่วง { res, err := กระบวนการ(งาน) if err != nil { errs < - err ดำเนินการต่อ } ผลลัพธ์ <- res } } ```
- ใช้คำสั่ง 'เลือก' : เมื่อรวมข้อมูลและช่องข้อผิดพลาด คุณสามารถใช้คำสั่ง 'เลือก' เพื่อฟังหลายช่องและดำเนินการตามค่าที่ได้รับ ``` ไปเลือก { case res := <-results: fmt.Println("Result:", res) case err := <-errs: fmt.Println("Error:", err) } ```
การกู้คืนจากความตื่นตระหนกใน Goroutines
หากต้องการฟื้นตัวจากความตื่นตระหนกใน goroutine คุณสามารถใช้คีย์เวิร์ด 'defer' พร้อมกับฟังก์ชันการกู้คืนที่กำหนดเองได้ ฟังก์ชันนี้จะทำงานเมื่อ goroutine ตื่นตระหนก และสามารถช่วยคุณจัดการและบันทึกข้อผิดพลาดได้อย่างสง่างาม ``` go func workerSafe() { เลื่อน func() { ถ้า r := กู้คืน(); r != ไม่มี { fmt.Println("Recovered from:", r) } }() // goroutine code here } ```
การเพิ่มประสิทธิภาพการทำงานพร้อมกันเพื่อประสิทธิภาพ
การปรับปรุงประสิทธิภาพของโปรแกรมที่ทำงานพร้อมกันใน Go ส่วนใหญ่เกี่ยวข้องกับการค้นหาความสมดุลที่เหมาะสมของการใช้ทรัพยากรและการใช้ประโยชน์สูงสุดจากความสามารถของฮาร์ดแวร์ ต่อไปนี้เป็นเทคนิคบางอย่างที่คุณสามารถใช้เพื่อเพิ่มประสิทธิภาพการทำงานของโปรแกรม Go พร้อมกันของคุณ:
- ปรับแต่งจำนวน goroutines : จำนวน goroutines ที่เหมาะสมขึ้นอยู่กับกรณีการใช้งานเฉพาะของคุณและข้อจำกัดของฮาร์ดแวร์ของคุณ ทดลองด้วยค่าต่างๆ เพื่อหาจำนวน goroutine ที่เหมาะสมที่สุดสำหรับแอปพลิเคชันของคุณ
- ใช้แชนเนลแบบบัฟเฟอร์ : การใช้แชนเนลแบบบัฟเฟอร์สามารถเพิ่มทรูพุตของงานพร้อมกัน ทำให้สามารถสร้างและใช้ข้อมูลได้มากขึ้นโดยไม่ต้องรอการซิงโครไนซ์
- ใช้การจำกัดอัตรา : การใช้การจำกัดอัตราในกระบวนการที่ใช้ทรัพยากรมากสามารถช่วยควบคุมการใช้ทรัพยากรและป้องกันปัญหา เช่น การโต้แย้ง การหยุดชะงัก และการโอเวอร์โหลดของระบบ
- ใช้การแคช : ผลลัพธ์จากการคำนวณแคชที่มีการเข้าถึงบ่อย ลดการคำนวณที่ซ้ำซ้อนและปรับปรุงประสิทธิภาพโดยรวมของโปรแกรมของคุณ
- สร้างโปรไฟล์แอปพลิเคชันของคุณ : สร้างโปรไฟล์แอปพลิเคชัน Go ของคุณโดยใช้เครื่องมือเช่น pprof เพื่อระบุและเพิ่มประสิทธิภาพคอขวดของประสิทธิภาพและงานที่ใช้ทรัพยากร
- ใช้ประโยชน์จาก AppMaster สำหรับแอปพลิเคชันแบ็กเอนด์ : เมื่อใช้แพลตฟอร์ม AppMaster แบบไม่มีโค้ด คุณสามารถสร้างแอปพลิเคชันแบ็กเอนด์ที่ใช้ประโยชน์จากความสามารถในการทำงานพร้อมกันของ Go เพื่อให้มั่นใจถึงประสิทธิภาพสูงสุดและความสามารถในการปรับขนาดสำหรับโซลูชันซอฟต์แวร์ของคุณ
ด้วยการเรียนรู้รูปแบบการทำงานพร้อมกันและเทคนิคการเพิ่มประสิทธิภาพเหล่านี้ คุณจะสามารถสร้างแอปพลิเคชันที่ทำงานพร้อมกันที่มีประสิทธิภาพและประสิทธิภาพสูงใน Go ใช้ประโยชน์จากคุณสมบัติการทำงานพร้อมกันในตัวของ Go ควบคู่ไปกับแพลตฟอร์ม AppMaster อันทรงพลังเพื่อยกระดับโครงการซอฟต์แวร์ของคุณให้สูงขึ้นไปอีกขั้น