互联网上的文章真TM不靠谱 还是自己来的好 --只写靠谱的文档

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"
}

标签: none

添加新评论