经典的 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")
}

3.编写一个程序,启动三个线程,三个线程的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()
}

4.交替打印两个数组

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(count *atomic.Int64) {
	for {
		time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond)
		if count.Load() > 0 {
			count.Add(-1)
			fmt.Println("Sale one, remain:", count.Load())
		} else {
			fmt.Println("Sold out")
			break
		}
	}
}
func main() {
	wg := &sync.WaitGroup{}
	ans := &atomic.Int64{}
	ans.Store(10)
	wg.Add(3)
	for i := 0; i < 3; i++ {
		go func() {
			Sale(ans)
			wg.Done()
		}()
	}
	wg.Wait()
}

生产者消费者

模拟化学反应 $2H_2 + O_2 = 2H_2O$

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)
}

如果本文帮助到了你,帮我点个广告可以咩(o′┏▽┓`o)


文章作者: Anubis
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Anubis !
评论
 本篇
经典的 Go 多线程编程题总结 经典的 Go 多线程编程题总结
本文通过具体的Go语言代码示例,详细介绍了实现线程同步和并发数据同步的多种方法。从基本的线程顺序控制到复杂的生产者消费者模型,这些示例覆盖了并发编程中的常见需求。通过通道、WaitGroup、Mutex、Atomic和条件变量等同步原语。
2024-02-12
下一篇 
使用中间件统计 gin 响应结果 使用中间件统计 gin 响应结果
如何使用中间件来统计 gin 的响应结果,主要用于 pv,uv 等统计
2024-02-07
  目录