併發寫 slice和map是不安全的
怎麼能高併發寫入?
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()))
}