go并发编程常用的一些方法及解释
go并发编程常用的一些函数
package main
import (
"fmt"
"sync"
"testing"
"time"
"golang.org/x/sync/singleflight"
)
type Lock struct {
count int
}
// 普通读锁
// 允许在共享资源上互斥访问(不能同时访问)
func TestSyncMutex(t *testing.T) {
l := &Lock{}
mu := &sync.Mutex{}
mu.Lock()
time.Sleep(1 * time.Second)
_ = l.count
defer mu.Unlock()
fmt.Println(time.Now().Format("2006-01-02 15:04:05"))
}
// 读写互斥锁,
// 提供了sync.Mutex的Lock和UnLock方法实现普通的读锁
// 它还允许使用RLock和RUnlock方法进行并发读取
func TestRWMutex(*testing.T) {
l := &Lock{}
rwMu := &sync.RWMutex{}
rwMu.RLock()
fmt.Println(time.Now().Format("开始执行 2006-01-02 15:04:05"))
time.Sleep(3 * time.Second)
fmt.Println(time.Now().Format("执行完 2006-01-02 15:04:05"))
l.count++
defer rwMu.RUnlock()
fmt.Println(time.Now().Format("锁执行完 2006-01-02 15:04:05"))
}
// sync.WaitGroup也是一个经常会用到的同步原语,它的使用场景是在一个goroutine等待一组goroutine执行完成。
// 使用Add(int) 增加计数器
// 使用Done() 或者add(-int) 减少计数器。
// 当计数器等于0时,则Wait()方法会立即返回。否则它将阻塞执行Wait()方法的goroutine直到计数器等于0时为止。
func TestWaitGroup(*testing.T) {
fmt.Println(time.Now().Format("开始 2006-01-02 15:04:05"))
wg := &sync.WaitGroup{}
for i := 0; i < 8; i++ {
wg.Add(1)
go func() {
time.Sleep(5 * time.Second)
fmt.Println(time.Now().Format("2006-01-02 15:04:05"))
defer wg.Done()
}()
}
wg.Wait()
fmt.Println(time.Now().Format("结束 2006-01-02 15:04:05"))
}
// 支持并发的map
// 解决在并发情况下,map写操作不安全的问题,
// 虽然可以加一个大锁来控制整个map的写入,但是效率存在问题
func TestMap(t *testing.T) {
m := sync.Map{}
m.Store(1, 1)
go func(m sync.Map) {
i := 0
for i < 10000 {
m.Store(1, 1)
i++
}
}(m)
go func(m sync.Map) {
i := 0
for i < 10000 {
m.Store(1, 1)
i++
}
}(m)
time.Sleep(1 * time.Second)
fmt.Println(m.Load(1))
}
// Pool 可以作为一个临时存储池,把对象当作一个临时对象存储在池中,然后进行存或取操作,这样对象就可以重用,不用进行内存分配,减轻 GC 压力
// 但是需要注意Pool是一个线程安全但是数据不安全的玩意,因为会被GC且没有提示,那么姑且就认为他是不安全的吧
func TestPool(t *testing.T) {
// 创建一个 Pool
pool := sync.Pool{
// New 函数用处:当我们从 Pool 中用 Get() 获取对象时,如果 Pool 为空,则通过 New 先创建一个
// 对象放入 Pool 中,相当于给一个 default 值
New: func() interface{} {
return "111"
},
}
name := "lilei"
num := 1
pool.Put(&name)
pool.Put(&num)
fmt.Println(pool.Get())
fmt.Println(pool.Get())
fmt.Println(pool.Get())
}
// 这个是用的比较多的了,我们在自己的框架里面,经常用来初始化数据库,日志这种,保证只会执行一次
// 这个例子里面,只会执行一次打印
func TestOnce(t *testing.T) {
once := &sync.Once{}
for i := 0; i < 4; i++ {
i := i
go func() {
once.Do(func() {
fmt.Printf("first %d\n", i)
})
}()
}
}
// 并发的访问同一组资源的时候,只允许一个请求进行,这个请求把结果告诉其它等待者,避免雪崩的现象。
// 比如cache 失效的时候,只允许一个goroutine从数据库中捞数据回种,避免雪崩对数据库的影响。
// 扩展库中提供。
// 但是我不怎么想用这个函数,因为我认为,如果一个缓存,很重要的量很大的,肯定用预热来处理,就完全不存在击穿的问题
// 如果说,你要在读取的时候做判断来直接更新缓存,就认为你可以接受比如在读取缓存时候的编发问题和击穿问题。
// 如果说既不做预热也不担心缓存失效时候的并发读取问题,我认为这是一个很极端的案例
func TestSingleflight(t *testing.T) {
g := new(singleflight.Group)
// 第1次调用
go func() {
v1, _, shared := g.Do("getData", func() (interface{}, error) {
ret := getData(1)
return ret, nil
})
fmt.Printf("1st call: v1:%v, shared:%v\n", v1, shared)
}()
time.Sleep(2 * time.Second)
// 第2次调用(第1次调用已开始但未结束)
v2, _, shared := g.Do("getData", func() (interface{}, error) {
ret := getData(1)
return ret, nil
})
fmt.Printf("2nd call: v2:%v, shared:%v\n", v2, shared)
}
func getData(id int64) string {
fmt.Println(time.Now().Format("本次有查询 2006-01-02 15:04:05"))
time.Sleep(10 * time.Second) // 模拟一个比较耗时的操作
return "liwenzhou.com"
}
叼茂SEO.bfbikes.com
叼茂SEO.bfbikes.com
看的我热血沸腾啊https://www.237fa.com/
想想你的文章写的特别好www.jiwenlaw.com