Go中特色同步工具

这里没有sync.WaitGroupcontext.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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值