这里没有sync.WaitGroup与context.Context篇幅,他们在另外一篇里面Go中Context&WaitGroup
sync.pool
粗浅原理
使用方法:
- 创建一个sync.Pool对象
给New属性赋值
用的时候直接用Get方法取 ,然后断言类型,再put进去
粗浅原理:
- 一个sync.Pool中有许多本地池(localPool),
- 一个本地池对应一个协程
- 一个本地池中有私有(private)和共享(share)两种属性
私有(private) 只能放置一个对象,只能被自己的协程调用
共享(share)能放置多个对象,能被其他协程调用 - Get方法获取对象顺序
从自己本地池(localPool)私有(private)中拿,(不用加锁)
从自己以及其他本地池(localPool)中共享(share)中拿,(需要加锁)
从其他线程上拿
自己New方法创建
package main
import (
"context"
"fmt"
"sync"
"time"
)
func main() {
p := sync.Pool{
New: func() interface{} {
return make(chan int)
},
}
ctx, cancel := context.WithCancel(context.Background())
c := p.Get().(chan int)
p.Put(c)
go func() {
for i := 1; i < 10; i++ {
c <- i
time.Sleep(time.Millisecond * 300)
}
cancel()
}()
go func() {
for {
fmt.Println(<-c)
}
}()
<-ctx.Done()
close(c)
}
普通测试例子
编译的时候使用 go build -gcflags="-l -N"
否则编译器会优化代码,反而没有pool的会很快
package main
import (
"fmt"
"sync"
"time"
)
// 一个[]byte的对象池,每个对象为一个[]byte
var bytePool2 = sync.Pool{
New: func() interface{} {
b := make([]byte, 1024)
return &b
},
}
func main() {
a := time.Now().UnixNano()
// 不使用对象池
for i := 0; i < 100000000; i++{
obj := make([]byte, 1024)
_=obj
}
b := time.Now().UnixNano()
// 使用对象池
for i := 0; i < 100000000; i++{
obj := bytePool2.Get().(*[]byte)
_ = obj
bytePool2.Put(obj)
}
c := time.Now().UnixNano()
fmt.Println("without pool ",b-a)
fmt.Println("with pool ",c-b)
}
sync.Map
这个结构要注意加载值和取值时候的类型判断,可以考虑用java中的构造函数思想来做,确保入参保证唯一类型,不过那样不同的map都要写,挺麻烦的
sync.Map结构源码:
type Map struct {
mu Mutex
// read contains the portion of the map's contents that are safe for
// concurrent access (with or without mu held).
//
// The read field itself is always safe to load, but must only be stored with
// mu held.
//
// Entries stored in read may be updated concurrently without mu, but updating
// a previously-expunged entry requires that the entry be copied to the dirty
// map and unexpunged with mu held.
read atomic.Value // readOnly
// dirty contains the portion of the map's contents that require mu to be
// held. To ensure that the dirty map can be promoted to the read map quickly,
// it also includes all of the non-expunged entries in the read map.
//
// Expunged entries are not stored in the dirty map. An expunged entry in the
// clean map must be unexpunged and added to the dirty map before a new value
// can be stored to it.
//
// If the dirty map is nil, the next write to the map will initialize it by
// making a shallow copy of the clean map, omitting stale entries.
dirty map[interface{}]*entry
// misses counts the number of loads since the read map was last updated that
// needed to lock mu to determine whether the key was present.
//
// Once enough misses have occurred to cover the cost of copying the dirty
// map, the dirty map will be promoted to the read map (in the unamended
// state) and the next store to the map will make a new dirty copy.
misses int
}
使用方法:
使用的时候为了保证键值的类型,解决方案:
1 可以像java中的构造方法一样,在sync.Map上在包一层来规定入参的类型.
2 可以使用断言和反射来判断类型
- 创建对象:var mapInt = new(sync.Map) 或者 mapInt := sync.Map{}
- CRUD:
增加:mapInt.Store(1,“波”)
删除: mapInt.Delete(2) 其中key不存在不会报错
查询:v,ok := mapInt.Load(2) 返回value和ok,未查到ok为false
修改:mapInt.Store(1,“波”) 若是key已经存在,则会修改
查询增加: v,ok := mapInt.LoadOrStore(2,“囧”)若是不存在返回false,并且将值加进去,
存在则返回true和value,loadOrStore不能更改value
粗浅原理:
- 用了read(只读 原子操作) dirty(读写,需要加锁)存储数据, 存储数据的键值对是:map[interface{}]entry,值是entry指针
- entry三种值
nil: entry已被删除了,并且m.dirty为nil
expunged: entry已被删除了,并且m.dirty不为nil,而且这个entry不存在于m.dirty中
其它: entry是一个正常的值 - 无论是增加修改,删除,查询,都会从read开始的,然后再到dirty
- 比如增加的话是从read开始的,read只能读,只是从read中读取这个值,看看有没有这个值,然后再加锁对dirty操作,给dirty增加了值
- 查的时候从read开始查,查不到就给Map里面的misses字段增加一,
当misses>=len(m.dirty)的长度时候,就把dirty提升为read,又开始从read中查询,read中是原子操作不需要加锁. - 删除的话read中标记为nil就可以了,dirty中是直接删除的
注意几点:
- LoadOrStore方法如果提供的key存在,则返回已存在的值(Load),否则保存提供的键值(Store)
- Range方法调用前会做一个m.dirty的提升
- map中放置取值要考虑类型判断
sync.Map优化点:
- 空间换时间。 通过冗余的两个数据结构(read、dirty),实现加锁对性能的影响。
- 使用只读数据(read),避免读写冲突。
- 动态调整,miss次数多了之后,将dirty数据提升为read。
- double-checking。(每次操作都是先查read中的数据,然后操作dirty加锁后再次查询read中数据)
- 延迟删除。 删除一个键值只是打标记,只有在提升dirty的时候才清理删除的数据。
- 优先从read读取、更新、删除,因为对read的读取不需要锁。
普通例子仅供参考
package main
import (
"fmt"
"sync"
)
//完整的来回顾这个流程:
//增加或者修改:我们执行增加(Store)操作,首先会检验read中是否存在这个值
// 存在的话而且没有被标记为nil则直接存储这这个值中;此时因为m.dirty也指向这个entry,dirty也保存的这个值,
// 若果不存在则给dirty增加这个值
// 或者被标记为nil的话,改变标记,更新值
func main() {
//var mapInt = new(sync.Map)
mapInt := sync.Map{}
//add element
mapInt.Store(1, "波")
mapInt.Store(2, "多")
mapInt.Store(3, "野")
fmt.Println("before delete key:")
//iterator
mapInt.Range(func(key, value interface{}) bool {
fmt.Println(key, value)
return true
})
//delete
mapInt.Delete(4)
fmt.Println("after delete key:")
//iterator
mapInt.Range(func(key, value interface{}) bool {
fmt.Println(key, value)
return true
})
//query
v, ok := mapInt.Load(2)
if ok {
fmt.Println(v)
}
//query or add
v, ok = mapInt.LoadOrStore(2, "囧")
fmt.Println("loadOrStore :", v, ok)
//iterator
fmt.Println("after query or add key:")
mapInt.Range(func(key, value interface{}) bool {
fmt.Println(key, value)
return true
})
//add
mapInt.Store(1, "囧")
//iterator
fmt.Println("after add key:")
mapInt.Range(func(key, value interface{}) bool {
fmt.Println(key, value)
return true
})
}
sync.Once
Do中的函数全局只执行一次,即使在循环里面,且开出多个goroutine
package main
import (
"fmt"
"sync"
)
func main() {
var one sync.Once
wg := new(sync.WaitGroup)
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
one.Do(onetest)
fmt.Println(i)
wg.Done()
}(i)
}
wg.Wait()
}
func onetest() {
fmt.Println("执行一次")
}
执行结果
执行一次
9
3
4
5
6
7
8
2
0
1
Process finished with exit code 0