做男性服装的网站,营销策划方案100例,微擎小程序,网站做次级页面之前用 go 写一个小工具的时候, 用到了多个协程之间的通信, 当时随手查了查, 结果查出来一大坨, 简单记录一下. golang中多个协程之间是如何进行通信及数据同步的嘞.
共享变量
一个最简单, 最容易想到的, 就是通过全局变量的方式, 多个协程读写同一个变量. 但对同一个变量的更…之前用 go 写一个小工具的时候, 用到了多个协程之间的通信, 当时随手查了查, 结果查出来一大坨, 简单记录一下. golang中多个协程之间是如何进行通信及数据同步的嘞.
共享变量
一个最简单, 最容易想到的, 就是通过全局变量的方式, 多个协程读写同一个变量. 但对同一个变量的更改, 就不得不加锁了, 否则极易引发数据问题. 一般系统库都提供基本的锁, go 也提供了.
package mainimport (fmtsynctime
)var num 0
// 互斥锁
var mutex sync.Mutex{}
// 读写锁
var rwMutex sync.RWMutex{}func main() {for i : 0; i 100; i {go incrNum()}time.Sleep(2)fmt.Println(num)
}func incrNum() {mutex.Lock()num num 1mutex.Unlock()
}仅执行一次
当查询锁查到sync这个模块时, 发现它下面的对象并没有几个, 都是针对协程同步的各个方面给出的解决方案. 所以我就一个一个看文档试了试.
当你需要对环境, 连接池等等资源进行初始化时, 这种操作只需要执行一次, 这时候就需要它了. sync.Once对象可以保证仅执行一次. 和 init 方法有些类似, 不过 init 方法是在模块首次加载时执行, 而sync.Once是在首次调用时执行. (其实现就是一个计数器加一个互斥锁)
package mainimport (fmtsynctime
)var num 0
var once sync.Once{}func main() {for i : 0; i 100; i {go once.Do(incrNum)}time.Sleep(2)fmt.Println(num)
}func incrNum() {num num 1
}等待其他协程处理
某个协程需要等第一阶段的所有协程处理完毕, 才能开始执行第二阶段. 这个时候, 等待其他协程就可以通过sync.WaitGroup 来实现. (当然, 也可以通过一个共享计数器变量来实现).
package mainimport (fmtsync
)var waitGroup sync.WaitGroup{}func main() {for i : 0; i 100; i {go incrNum()}// 等待其他协程处理完毕(共享变量为0)waitGroup.Wait()fmt.Println(don)
}func incrNum() {// 增加需要等待的协程数量(共享变量1)waitGroup.Add(1)// do something// 标记当前协程处理完成(共享变量-1)waitGroup.Done()
}消息通知
多个协程启动时, 等待某个命令到来时执行命令, 唤醒等待协程. go 对此类操作也进行了处理, 感觉好贴心哦. 但是经过测试, 即使没有空闲的协程, 唤醒命令同样能够发出去, 所以需要注意一下.
package mainimport (sync
)var mutex sync.Mutex{}
var cond sync.NewCond(mutex)func main() {for i : 0; i 100; i {go incrNum()}// 发送命令给一个随机获得锁的协程cond.Signal()// 发送命令给所有获得锁的协程cond.Broadcast()
}func incrNum() {// 获取锁, 标识当前协程可以处理命令cond.L.Lock()// 可添加退出执行命令队列的条件for true {// 等待命令cond.Wait()// do something}// 释放锁, 标记命令处理完毕, 退出协程cond.L.Unlock()
}多协程 map
普通的 map 在多协程操作时, 是不支持并发写入的. go贴心的给封装了支持并发写入的map. 同时也提供了针对map的基本操作.
package mainimport (fmtsynctime
)var m sync.Map{}func main() {for i : 0; i 100; i {go func() {m.Store(1, 1)}()}time.Sleep(time.Second * 2)// 遍历 mapm.Range(func(key, value interface{}) bool {// 返回 false 结束遍历return true})// 读取变量, 若不存在则设置m.LoadOrStore(1, 3)// 删除 keym.Delete(1)// 读取变量load, _ : m.Load(1)fmt.Println(load)
}
多协程对象池
对于数据库连接池应该并不陌生. 而sync.Pool对象是go封装的协程安全的对象池. 对象池的使用十分简单, 存/取
package mainimport (sync
)var p sync.Pool{// 当池子中没有对象了, 用于创建新对象New: func() interface {}{return 3},
}func main() {// 从池子中获取一个对象r : p.Get()// 用完后将对象放回池子中p.Put(r)
}sync 简单总结
针对go系统的sync模块, 提供的基础功能如下:
互斥锁 Mutex读写锁 RWMutex函数单次执行 Once协程执行等待 WaitGroup协程消息通知 Cond多协程 map Map多协程对象池 Pool
几个都简单试过之后, 发现sync模块针对常用的几个多协程工具进行了封装, 想来可以基本满足日常使用了.
终极通信-channel
channel是一个协程安全的通信管道, 简单理解为数据从一侧放入, 从另一侧拿出. 这玩意感觉能玩出花来, 还不太理解, 留到国庆研究.