Golang的 sync包提供了大量的线程同步操做的类. 因此知足咱们平常使用. 包含锁,和安全的集合. 其中,关于golang 各类锁的实现,请看这个文章,我我的感受写的不错juejin.im/entry/5ed32…java
第一种就是 Mutex
为互斥锁 , 实现跟Java的reentrantlock很像. 基本都是自旋锁,golang
咱们下面有一个场景 , 将变量x累加50000次 咱们开启5个goroutine去执行安全
func main() {
var x = 0
for num := 0; num < 4; num++ {
go func() {
for i := 0; i < 10000; i++ {
increase(&x)
}
}()
}
// 等待子线程退出
time.Sleep(time.Millisecond * 2000)
fmt.Println(x)
}
// 这个操做是Java作不了的.只有C,C++和Golang能够
func increase(x *int) {
*x = *x + 1
}
复制代码
若是输出结果是 50000那就对了, 咱们看看结果多线程
23283
复制代码
为何呢, 由于多线程同时操做一个变量时就会出现这种问题 , 出现读写不一致的问题 , 如何处理呢函数
咱们申明一个锁,让他同步执行 , 这么申明就能够了ui
var ILock = sync.Mutex{}
func main() {
var x = 0
for num := 0; num < 5; num++ {
go func() {
for i := 0; i < 10000; i++ {
increase(&x)
}
}()
}
// 等待子线程退出
time.Sleep(time.Millisecond * 2000)
fmt.Println(x)
}
func increase(x *int) // 1. 先加锁 ILock.Lock() // 执行完方法释放锁. 跟Java的很像,可是不是reentrant defer ILock.Unlock() *x = *x + 1 } 复制代码
此时执行 发现结果spa
50000
复制代码
输出正确.线程
其实还能够这么写. 效率更高指针
go func() {
ILock.Lock()
defer ILock.Unlock()
for i := 0; i < 10000; i++ {
increase(&x)
}
}()
复制代码
var (
lock = make(chan int, 1)
)
func Lock() {
lock <- 1
}
func Unlock() {
<-lock
}
复制代码
此时这就是一个最简单的锁.code
跟Java的如出一辙 , 我就很少说了, 读写互斥, 可多读(读锁能够同时存在多个), 但读写不能同时存在, 写互斥
func main() {
// 读写锁
mutex := sync.RWMutex{}
go func() {
// 读锁
mutex.RLocker().Lock()
fmt.Println("读")
mutex.RLocker().Unlock()
}()
go func() {
mutex.RLocker().Lock()
fmt.Println("读")
time.Sleep(time.Millisecond * 5000)
mutex.RLocker().Unlock()
}()
time.Sleep(time.Millisecond * 1000)
go func() {
// 写锁 , 就是普通的锁了
fmt.Println("拿到了")
mutex.Lock()
fmt.Println("写")
mutex.Unlock()
}()
time.Sleep(time.Millisecond * 6000)
}
复制代码
输出
读
读
拿到了 // 耗时1ms
写 // 对比前一步耗时 ms
复制代码
这个类相似于Java的
CountDownLatch
类, 可是比Java的好点. 第一Java的须要初始化告诉他计数器是多少, 因此他只有一个countdown和wait操做. 可是
sync.WaitGroup
倒是三个操做, 第一个 down , add(int) , wait 三个操做. 因此他实现更加好.
简单以第一个例子为例子吧, 好比说 , 咱们不知道goroutine啥时候退出是吧, 可是有了这玩意就知道了.
func main() {
var WG = sync.WaitGroup{}
start := time.Now().UnixNano() / 1e6
for num := 0; num < 5; num++ {
WG.Add(1)
go func(num int) {
defer WG.Done()
ran := rand.Int63n(1000)
fmt.Printf("goroutine-%d sleep : %dms\n", num, ran)
time.Sleep(time.Millisecond * time.Duration(ran))
}(num)
}
// 等待子线程退出
WG.Wait()
fmt.Printf("main waitting : %dms\n", time.Now().UnixNano()/1e6-start)
}
复制代码
输出 : 根据木桶原理, 耗时最长的是937ms, 因此主线程等待了939ms.
goroutine-1 sleep : 410ms
goroutine-0 sleep : 821ms
goroutine-2 sleep : 51ms
goroutine-3 sleep : 937ms
goroutine-4 sleep : 551ms
main waitting : 939ms
复制代码
因为
sync.WaitGroup
也是一个对象Structs, 因此须要指针传递, 不能使用值传递, 注意一下.由于状态值复制了就无效了.
根据封装, 咱们须要传递 sync.waitgroup .
func fun(num int, wg *sync.WaitGroup) {
defer wg.Done()
ran := rand.Int63n(1000)
fmt.Printf("goroutine-%d sleep : %dms\n", num, ran)
time.Sleep(time.Millisecond * time.Duration(ran))
}
复制代码
而后main方法
func main() {
var WG = &sync.WaitGroup{}
start := time.Now().UnixNano() / 1e6
for num := 0; num < 5; num++ {
WG.Add(1)
go fun(num, WG)
}
// 等待子线程退出
WG.Wait()
fmt.Printf("main waitting : %dms\n", time.Now().UnixNano()/1e6-start)
}
复制代码
输出 :
goroutine-4 sleep : 410ms
goroutine-1 sleep : 551ms
goroutine-0 sleep : 821ms
goroutine-2 sleep : 51ms
goroutine-3 sleep : 937ms
main waitting : 939ms
复制代码
这个玩意可让 once.Do()
方法只执行一次. 其实相似于一个flag,一开始为true. 每次执行判断是否为true , 当执行了一次之后改为false. 其实他就是这个原理 , 不过他使用了cas , 保证了线程安全性,
func main() {
once := sync.Once{}
one(&once)
one(&once)
one(&once)
}
func one(once *sync.Once) {
fmt.Println("执行函数")
once.Do(func() {
fmt.Println("只会执行了一次")
})
}
复制代码
输出
执行函数
只会执行了一次
执行函数
执行函数
复制代码
因此他能够只执行一次 , 适合作初始化操做, 或者其余一次性的操做, 不须要屡次
提供的线程安全的map , 多线程访问时, 对于crud 操做, 会加锁.
maps := sync.Map{}
// 存
maps.Store("","")
// 删
maps.Delete("")
// 取
maps.Load("")
// 有就不存,返回已经存了的对象和true, 若是没有就返回存的value和false.
maps.LoadOrStore("","")
复制代码
顾名思义一个池子, 那么咱们看看这个池子主要作啥了.
Pool用于存储那些被分配了可是没有被使用,而将来可能会使用的值,以减少垃圾回收的压力。(适合大对象)
同时他提供了一个存储的地方. 减小大量实例化过程 . 可是效率未必要比实例化快奥 . 由于维护一个对象要考虑各类问题, 这就是效率 , 可是实例化啥也不用考虑.
操做很简单.
func main() {
pool := &sync.Pool{
New: func() interface{} {
return "new"
},
}
// 首先的放一个 , 因为put操做.
pool.Put("init")
go func() {
// 拿一个
s := pool.Get().(string)
// 使用
fmt.Println(s)
// 使用完放进去, 因此特别适合大对象.
pool.Put(s)
}()
}
复制代码
相似与Java的wait和notify 或者说 Condition ,更像后者,可是没有超时的机制
func main() {
mutex := sync.Mutex{}
start := sync.NewCond(&mutex)
for x := 0; x < 10; x++ {
go func() {
start.L.Lock()
defer start.L.Unlock()
start.Wait()
fmt.Println("do work")
}()
}
go func() {
time.Sleep(time.Second * 1)
start.Broadcast()
}()
time.Sleep(time.Second * 2)
}
复制代码