golang 并发-共享资源安全
首先,我们需要知道:在golang中,多 goroutine 同时操作一个共享资源时;我们需要保障资源的安全(我们对资源的操作结果要符合我们的预期);
当我们未对资源做保护操作时,多个goroutine同时操作同一资源时,就可能会出现问题;
例如 count=1 ,有A、B两个 goroutine 同时拿到资源 count 变量,再对 count 进行更新操作,
然后再打印count;
A 拿到 count 变量(资源)时,count 等于1,
B 拿到 count 变量(资源)时,count 也等于1,
A 对于变量操作一次(进行一次 +1 ),这是 count 就等于2了,count的值被修改成2 ;
B 对于变量操作两次(进行两次 +1 ),这是 count 就等于3了,count的值被修改成3 ;
若 A 先执行完,则 B: count 的值被修改成3;为 count 的最后一次更新,count打印 就为 3;
若 B 先执行完,则 A: count 的值被修改成2;为 count 的最后一次更新,count打印 就为 2;
如若想资源被操作、更新后的结果符合我们的预期,我们就需要使用到 锁, 在 golang sync 包中,可以找到相关使用介绍,较为常用的锁相关有 sync.Map(同步Map,并发安全),sync.Mutex(互斥锁),sync.RWMutex(读写锁)。
举个例子,上代码:
未使用并发版本
package main
import "fmt"
var count = 0
func main() {
for i := 0; i < 1000; i++ {
addOne()
}
fmt.Println("count:", count)
}
func addOne() {
count += 1
}
/* 输出结果
count: 1000
*/
多次运行,输出结果
使用并发版本, 但未使用锁
package main
import (
"fmt"
"sync"
)
var count = 0
func main() {
wg := sync.WaitGroup{}
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
addOne()
wg.Done()
}()
}
wg.Wait()
fmt.Println("count:", count)
}
func addOne() {
count += 1
}
运行多次,都不符合预期-
注意:如果符合预期1000,可能时电脑性能导致的<我设置成100时,与未多并发程序结果一致>,可加大测试for循环次数,多次验证
多并发,且加锁
package main
import (
"fmt"
"sync"
)
var count = 0
var mutex = sync.Mutex{}
func main() {
wg := sync.WaitGroup{}
for i := 0; i < 10000; i++ {
wg.Add(1)
go func() {
addOne()
wg.Done()
}()
}
wg.Wait()
fmt.Println("count:", count)
}
func addOne() {
mutex.Lock()
defer mutex.Unlock()
count += 1
}
运行多次 结果一致
** 注意:** 这里我故意将循环次数调整至 100000 ,以避免一定的偶然性
代码优化
常见写法
package main
import (
"fmt"
"sync"
)
// 将计数、互斥锁(count、sync.Mutex)放入结构体中
type Count struct {
count int
sync.Mutex
}
var count Count
func main() {
wg := sync.WaitGroup{}
for i := 0; i < 100000; i++ {
wg.Add(1)
go func() {
addOne()
wg.Done()
}()
}
wg.Wait()
fmt.Println("count:", count.count)
}
func addOne() {
count.Lock()
defer count.Unlock()
count.count += 1
}
总结:
多并发程序,需要多注意临界区(共享)资源的更新问题;
未编写单元测试;总觉得该测试不够正规的;哈哈~