【golang】優雅且安全的併發寫slice或map

併發寫 slice和map是不安全的

【golang面試準備】驗證map&slice併發不安全

怎麼能高併發寫入?

slice&map併發不安全,都是在寫的時候發生的,那麼就要保證同一時間只有一個gorutine來寫這個slice或者map。

有兩種方式實現:

  • 加鎖方式:n個goroutine都有可能執行寫入操作,保證同一時間只能有一個在執行寫操作。 加鎖操作簡單,適用於性能要求低和邏輯不復雜的場景。
package main

import (
	"fmt"
	"sync"
)

func main() {
	slc := []int{}

	n := 10000
	var wg sync.WaitGroup
	var lock sync.Mutex
	wg.Add(n)
	for i := 0; i < n; i++ {
		go func(a int) {
			lock.Lock()
			slc = append(slc, a)
			lock.Unlock()
			wg.Done()
		}(i)
	}
	wg.Wait()

	fmt.Println("done len:", len(slc))
}
  • Active Object方式:只有1個goroutine在執行寫操作。避免多個goroutine競爭鎖。 適合業務場景複雜,性能要求高的場景。
package main

import (
	"fmt"
	"sync"
)

// active object對象
type Service struct {
	channel chan int `desc:"即將加入到數據slice的數據"`
	data    []int    `desc:"數據slice"`
}

// 新建一個size大小緩存的active object對象
func NewService(size int, done func()) *Service {
	s := &Service{
		channel: make(chan int, size),
		data:    make([]int, 0),
	}

	go func() {
		s.schedule()
		done()
	}()
	return s
}

// 把管道中的數據append到slice中
func (s *Service) schedule() {
	for v := range s.channel {
		s.data = append(s.data, v)
	}
}

// 增加一個值
func (s *Service) Add(v int) {
	s.channel <- v
}

// 管道使用完關閉
func (s *Service) Close() {
	close(s.channel)
}

// 返回slice
func (s *Service) Slice() []int {
	return s.data
}

func main() {

	// 1. 新建一個active object, 並增加結束信號
	c := make(chan struct{})
	s := NewService(100, func() { c <- struct{}{} })

	// 2. 起n個goroutine不斷執行增加操作
	n := 10000
	var wg sync.WaitGroup
	wg.Add(n)
	for i := 0; i < n; i++ {
		go func(a int) {
			s.Add(a)
			wg.Done()
		}(i)
	}
	wg.Wait()
	s.Close()

	<-c

	// 3. 校驗所有結果是否都被添加上
	fmt.Println("done len:", len(s.Slice()))
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章