开发者

GoLang切片并发安全解决方案详解

开发者 https://www.devze.com 2022-12-03 14:21 出处:网络 作者: ~庞贝
目录1.介绍切片并发问题2.实践检验真理3.回答切片并发安全问题4.解决切片并发安全问题方式5.附1.介绍切片并发问题
目录
  • 1.介绍切片并发问题
  • 2.实践检验真理
  • 3.回答切片并发安全问题
  • 4.解决切片并发安全问题方式
  • 5.附

1.介绍切片并发问题

关于切片的,Go语言中的切片原生支持并发吗?

2.实践检验真理

实践是检验真理的唯一标准,所以当我们遇到一个不确定的问题,直接写demo来验证,因为切片的特点,我们可以分多种情况来验证

1.不指定索引,动态扩容并发向切片添加数据

2.指定索引,指定容量并发向切片添加数据

  • 不指定索引,动态扩容并发向切片添加数据

不指定索引,动态扩容并发向切片添加数据:

通过打印数据发现每次len与cap的结果都不一致

func concurrentAppendSliceNotForceIndex() {
	sl := make([]int, 0)
	wg := sync.WaitGroup{}
	for index := 0; index < 100; index++ {
		k := index
		wg.Add(1)
		go func(num int) {
			sl = append(sl, num)
			wg.Done()
		}(k)
	}
	wg.Wait()
	fmt.Println(sl)
	fmt.Printf("final len(sl)=%d cap(sl)=%d\n", len(sl), cap(sl))
}
func main() {
	concurrentAppendSliceNotForceIndex()
	/*第一次运行代码后,输出:[2 0 1 5 6 7 8 9 10 4 17 11 12 13 14 15 16 21 18 19 20 23 22 24 25 26 39 27 28 29 30 31 35 55 54 56 57 58 59 60 61 62 64 63 65 6开发者_JAVA6 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 86 91 92 93 94 96 95 97 98 99]
	final len(sl)=74 cap(sl)=128*/
	//第二次运行代码后,输出:省略切片元素输出... final len(sl)=81 cap(sl)=128
	//第二次运行代码后,输出:省略切片元素输出... final len(sl)=77 cap(sl)=128
}
  • 指定索引,指定容量并发向切片添加数据

指定索引,指定容量并发向切片添加数据:

通过结果我们可以发现符合我们的预期,长度和容量都是100

func concurrentAppendSliceForceIndex() {
	sl := make([]int, 100)
	wg := sync.WaitGroup{}
	for index := 0; index < 100; index++ {
		k := index
		wg.Add(1)
		go func(num int) {
			sl[num] = num
			wg.Done()
		}(k)
	}
	wg.Wait()
	fmt.Println(sl)
	fmt.Printf("final len(sl)=%d cap(sl)=%d\n", len(sl), cap(sl))
}
func main()php {
	concurrentAppendSliceForceIndex()
	/*第一次运行代码后,输出:[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 6javascript2 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 7
	9 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99]
	final len(sl)=100 cap(sl)=100*/
	/*第一次运行代码后,输出:[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 7
	9 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99]
	final len(sl)=100 cap(sl)=100*/
	/*第一次运行代码后,输出:[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 7
	9 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99]
	final len(sl)=100 cap(sl)=100*/
}

3.回答切片并发安全问题

我们都知道切片是对数组的抽象,python其底层就是数组,在并发下写数据到相同的索引位会被覆盖,并且切片也有自动扩容的功能,当切片要进行扩容时,就要替换底层的数组,在切换底层数组时,多个goroutine是同时运行的,哪个goroutine先运行是不确定的,不论哪个goroutine先写入内存,肯定就有一次写入会覆盖之前的写入,所以在动态扩容时并发写入数组是不安全的;

所以当别人问你slice支持并发时,你就可以这样回答它:

当指定索引使用切片时,切片是支持并发读写索引区的数据的,但是索引区的数据在并发时会被覆盖的;当不指定索引切片时,并且切片动态扩容时,并发场景下扩容会被覆盖,所以切片是不支持并发的~。

4.解决切片并发安全问题方式

针对上述问题,我们可以多种方法来解决切片并发安全的问题:

1.加互斥锁

2.使用channel串行化操作

3.使用sync.map代替切片

5.附

设置为1的的时候,runtime.GOMAXPROCS(1)

package main
import (
	"fmt"
	"runtime"
	"sync"
)
func concurrentAppendSliceNotForceIndex() {
	sl := make([]int, 0)
	wg := sync.WaitGroup{}
	for index := 0; index < 100; index++ {
		k := index
		wg.Add(1)
		go func(num int) {
			sl = append(sl, num)
			wg.Done()
		}(k)
	}
	wg.Wait()
	fmt.Println(sl)
	fmt.Printf("final len(sl)=%d cap(sl)=%d\n", len(sl), cap(sl))
}
func main() {
	runtime.GOMAXPROCS(1)
	concurrentAppendSliceNotForceIndex()
	/*
		[99 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
		 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 5
		5 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
		82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98]
		final len(sl)=100 cap(sl)=128
	*/
	/*
		[13 0 1 2 3 4 5 6 7 8 9 10 11 12 99 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
		 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 5
		5 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
		82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98]
		final len(sl)=100 cap(sl)=128
	*/
	/*
		[10 0 1 2 3 4 5 6 7 8 9 99 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
		 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 5
		5 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
		82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98]
		final len(sl)=100 cap(sl)=128
	*/
}
package main
import (
	"fmt"
	"runtime"
	"sync"
)
var wg sync.WaitGroup
var sl []int
func add() {
	for index := 0; index < 100; index++ {
		sl = append(sl, index)
	}
	wg.Done()
}
func main() {
	runtime.GOMAXPROCS(1)
	wg.Add(1)
	go add()
	wg.Wait()
	//无论执行多少次都输出一下结果
	fmt.Println(sl)
	fmt.Printf("final len(sl)=%d cap(sl)=%d\n", len(sl), cap(sl))
	/*
		[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 6
		3 64 6php5 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99]
		final len(sl)=100 cap(sl)=128
	*/
}
package main
import (
	"fmt"
	"runtime"
	"sync"
)
var wg sync.WaitGroup
var sl []int
func add() {
	for index := 0; index < 50; index++ {
		sl = append(sl, index)
	}
	wg.Done()
}
func main() {
	runtime.GOMAXPROCS(1)
	wg.Add(2)
	go add()
	go add()
	wg.Wait()
	//无论执行多少次都输出一下结果
	fmt.Println(sl)
	fmt.Printf("final len(sl)=%d cap(sl)=%d\n", len(sl), cap(sl))
	/*
			[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
		 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49]
		final len(sl)=100 cap(sl)=128
	*/
}

不限数量:

package main
import (
	"fmt"
	"sync"
)
var wg sync.WaitGroup
var sl []int
func add() {
	for index := 0; index < 50; index++ {
		sl = append(sl, index)
	}
	wg.Done()
}
func main() {
	wg.Add(2)
	go add()
	go add()
	wg.Wait()
	fmt.Println(sl)
	fmt.Printf("final len(sl)=%d cap(sl)=%d\n", len(sl), cap(sl))
	/*
				[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
		 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49]
		final len(sl)=82 cap(sl)=128
	*/
}

加锁

package main
import (
	"fmt"
	"sync"
)
var wg sync.WaitGroup
var sl []int
var lock sync.Mutex
func add() {
	for index := 0; index < 50; index++ {
		lock.Lock()
		sl = append(sl, index)
		lock.Unlock()
	}
	wg.Done()
}
func main() {
	wg.Add(2)
	go add()
	go add()
	wg.Wait()
	fmt.Println(sl)
	fmt.Printf("final len(sl)=%d cap(sl)=%d\n", len(sl), cap(sl))
	/*
		[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
		17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49]
		final len(sl)=100 cap(sl)=128

	*/
	/*
		[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
		 30 31 32 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
		 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49php 33 34 35 3
		6 37 38 39 40 41 42 43 44 45 46 47 48 49]
		final len(sl)=100 cap(sl)=128
	*/
}

到此这篇关于golang切片并发安全解决方案详解的文章就介绍到这了,更多相关GoLang切片并发安全内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号