经典的 Go 多线程编程题总结
线程同步类
此类题目一般是指定线程的输出顺序,或者是不同线程之间存在依赖关系。
1.要求线程a执行完才开始线程b, 线程b执行完才开始线程
package main import ( "fmt" "sync" ) //有明确的前后阻塞条件 func print(nums []int, preChan chan struct{}, postChan chan struct{}, wg *sync.WaitGroup) { <-preChan for _, num := range nums { fmt.Println(num) } postChan <- struct { }{} wg.Done() } func main() { ch1 := make(chan struct{}, 1) ch2 := make(chan struct{}, 1) wg := &sync.WaitGroup{} nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} wg.Add(2) go print(nums[:2], ch2, ch1, wg) go print(nums[2:], ch1, ch2, wg) ch2 <- struct{}{} wg.Wait() close(ch1) close(ch2) fmt.Println("done") }
2.两个线程轮流打印数字,一直到100
package main import ( "fmt" "sync" ) func print(preChan chan int, postChan chan int, wg *sync.WaitGroup) { for { select { case i := <-preChan: if i > 100 { wg.Done() postChan <- i + 1 return } fmt.Println(i) postChan <- i + 1 } } } func main() { ch1 := make(chan int, 1) ch2 := make(chan int, 1) wg := &sync.WaitGroup{} wg.Add(2) go print(ch2, ch1, wg) go print(ch1, ch2, wg) ch2 <- 1 wg.Wait() close(ch1) close(ch2) fmt.Println("done") }
4、编写一个程序,启动三个线程,三个线程的ID分别是A,B,C;,每个线程将自己的ID值在屏幕上打印5遍,打印顺序是ABCABC…
package main import ( "fmt" "sync" ) func print(id string, preChan <-chan struct{}, nextChan chan<- struct{}, wg *sync.WaitGroup) { for i := 0; i < 5; i++ { <-preChan fmt.Print(id) nextChan <- struct{}{} } wg.Done() } func main() { wg := &sync.WaitGroup{} wg.Add(3) ch1 := make(chan struct{}, 1) ch2 := make(chan struct{}, 1) ch3 := make(chan struct{}, 1) go print("A", ch1, ch2, wg) go print("B", ch2, ch3, wg) go print("C", ch3, ch1, wg) ch1 <- struct{}{} wg.Wait() }
5.交替打印两个数组
package main import ( "fmt" "sync" ) func Print[T any](data []T, preChan <-chan struct{}, nextChan chan<- struct{}) { for i := 0; i < len(data); i++ { <-preChan fmt.Println(data[i]) nextChan <- struct{}{} } } func main() { ch1, ch2 := make(chan struct{}, 1), make(chan struct{}, 1) array1 := []int{1, 2, 3, 4, 5} array2 := []string{"a", "b", "c", "d", "e"} wg := sync.WaitGroup{} wg.Add(2) go func() { Print(array1, ch1, ch2) wg.Done() }() go func() { Print(array2, ch2, ch1) wg.Done() }() ch1 <- struct{}{} wg.Wait() }
并发数据同步类
此类型一般是在多并发情景下保证数据的一致性。
1.编写10个线程,第一个线程从1加到10,第二个线程从11加20…第十个线程从91加到100,最后再把10个线程结果相加。
package main import ( "fmt" "sync" "sync/atomic" ) func Sum(nums []int, ans *atomic.Int32) { tmp := 0 for _, v := range nums { tmp += v } ans.Add(int32(tmp)) } func main() { wg := &sync.WaitGroup{} ans := &atomic.Int32{} wg.Add(10) for i := 0; i <= 9; i++ { go func(n int, ans *atomic.Int32) { defer wg.Done() nums := make([]int, 0, 10) for i := 1; i <= 10; i++ { nums = append(nums, i+n*10) } Sum(nums, ans) }(i, ans) } wg.Wait() fmt.Println(ans.Load()) }
2.三个窗口同时卖票
由于此处的场景比较简单,仅需要更新余票数目,因此我们使用 atomic 会更加高效。
而在需要维护的数据较为复杂时,更应该使用 mutex,atomic 适合维护的数据是整数或是布尔值的情况。
package main import ( "fmt" "math/rand" "sync" "sync/atomic" "time" ) func Sale(total *atomic.Int64, ID string, wg *sync.WaitGroup, start *sync.Cond, mutex *sync.Mutex, ready *bool) { start.L.Lock() for !*ready { start.Wait() } start.L.Unlock() for { mutex.Lock() if total.Load() > 0 { total.Add(-1) fmt.Println(ID + " buy a ticket") mutex.Unlock() } else { mutex.Unlock() break } time.Sleep(time.Millisecond*time.Duration(rand.Intn(500)) + time.Millisecond*500) } wg.Done() } func main() { total := atomic.Int64{} total.Store(100) wg := sync.WaitGroup{} wg.Add(3) cond := sync.NewCond(&sync.Mutex{}) mutex := sync.Mutex{} ready := false go Sale(&total, "1", &wg, cond, &mutex, &ready) go Sale(&total, "2", &wg, cond, &mutex, &ready) go Sale(&total, "3", &wg, cond, &mutex, &ready) ready = true cond.Broadcast() wg.Wait() }
生产者消费者
模拟化学反应
package main import ( "fmt" "sync" "sync/atomic" "time" ) var ( h2Count atomic.Int32 o2Count atomic.Int32 fireCond = sync.NewCond(&sync.Mutex{}) H2Cond = sync.NewCond(&sync.Mutex{}) O2Cond = sync.NewCond(&sync.Mutex{}) ) func react(id string) { for { // 1. 生成需要两份的氢气 H2Cond.L.Lock() if h2Count.Load() < 2 { H2Cond.Wait() } h2Count.Add(-2) H2Cond.L.Unlock() // 2. 生成需要一份的氧气 O2Cond.L.Lock() if o2Count.Load() < 1 { O2Cond.Wait() } o2Count.Add(-1) O2Cond.L.Unlock() // 3. 生成水 fireCond.L.Lock() fireCond.Wait() fireCond.L.Unlock() fmt.Println(id + "生成水") } } func produceHydrogen() { ticker := time.NewTicker(1 * time.Second) for { select { case <-ticker.C: h2Count.Add(1) if h2Count.Load() >= 2 { H2Cond.Signal() } } } } func produceOxygen() { ticker := time.NewTicker(1 * time.Second) for { select { case <-ticker.C: o2Count.Add(1) O2Cond.Signal() } } } func produceFire() { ticker := time.NewTicker(1 * time.Second) for { select { case <-ticker.C: fireCond.Broadcast() } } } func main() { go produceOxygen() go produceHydrogen() go produceHydrogen() go produceHydrogen() go produceFire() go react("1") go react("2") time.Sleep(1 * time.Minute) }