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

并发数据同步类

此类型一般是在多并发情景下保证数据的一致性。

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 适合维护的数据是整数或是布尔值的情况。

使用 cond 来进行同步启动

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

生产者消费者

模拟化学反应 $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 !
评论
  目录