client-go和golang源码中的技巧


client-go和golang源码中的技巧文章插图
client-go中有很多比较有意思的实现 , 如定时器 , 同步机制等 , 可以作为移植使用 。 下面就遇到的一些技术讲解 , 首先看第一个:

  • sets.String(k8s.io/apimachinery/pkg/util/sets/string.go)
实现了对golang map的key的处理 , 如计算交集 , 并集等 。 实际中可能会遇到需要判断两个map的key是否重合的场景 , 此时可以使用下述方式实现 , sets.StringKeySet函数将入参的map的key抽取成一个String类型 , 这样就可以使用String的方法操作key
ps:更多功能参见源码
package mainimport ("fmt""k8s.io/apimachinery/pkg/util/sets")func main(){map1 := map[string]int{"aaa":1,"bbb":2,"ccc":3}map2 := map[string]int{"ccc":1,"ddd":2,"eee":3}newmap1 := sets.StringKeySet(map1)newmap2 := sets.StringKeySet(map2)fmt.Println(newmap1.List(),newmap2.List())fmt.Println(newmap1.HasAny(newmap2.List()...)) //3个点用于把数组打散为单个元素}结果:true
  • 同步机制sync.Mutex(golang 内置方法) , 用于数据同步
有2个方法:
func (m *Mutex) Lock()func (m *Mutex) Unlock()类似C语言线程的互斥锁 , 用于对数据进行加解锁操作 。 当数据被加锁后 , 未获得该锁的程序将无法读取被加锁的数据 。 从下面例子可以看出在数据被解锁前其他协程无法对该数据进行读写操作 。
ps: read data的数据也可能为“data”
【client-go和golang源码中的技巧】package mainimport ("fmt""sync")type LockTest struct {l sync.Mutexdata string}func main(){lockTest := LockTest{sync.Mutex{},"data"}go func() {lockTest.l.Lock()fmt.Println("sleep begin")time.Sleep(time.Second*2)fmt.Println("sleep end")lockTest.l.Unlock()}()time.Sleep(time.Second*1)go func() {lockTest.l.Lock()fmt.Println("read data:",lockTest.data)lockTest.l.Unlock()}()go func() {lockTest.l.Lock()fmt.Println("write data begin")lockTest.data="http://kandian.youth.cn/index/new data"fmt.Println("write data end")lockTest.l.Unlock()}()time.Sleep(time.Second*5)}结果:sleep beginsleep endwrite data beginwrite data endread data: new data
  • sync.RWMutex(golang 内置方法) , 用于数据同步
读写锁 , 含4个方法 , 前2个为读锁 , 后2个为写锁 , 使用时要一一对应 。 写锁会阻塞读写操作 , 读锁不会阻塞写操作 , 读锁可以有多个 , 读锁之间不会相互阻塞 , 适用于读多写少的场景 。 因此如果单纯使用RWMutex.Lock/RWMutex.UnLock与使用Mutex.Lock/Mutex.UnLock效果相同
func (rw *RWMutex) RLock()func (rw *RWMutex) RUnlock()func (rw *RWMutex) Lock()func (rw *RWMutex) Unlock()读写锁一般是读锁和写锁结合使用的 。 在有写锁的时候 , 读锁会被阻塞 , 等待写锁释放后才能进行读操作 。
ps:sync.Mutex和sync.RWMutex一般都是内置在结构体中使用 , 用于保护本结构体的数据
package mainimport ("fmt""sync")type LockTest struct {l sync.RWMutexdata string}func main(){lockTest := LockTest{sync.RWMutex{},"data"}go func() {lockTest.l.Lock()fmt.Println("write data begin")lockTest.data="http://kandian.youth.cn/index/new data"time.Sleep(time.Second*3)fmt.Println("write data end")lockTest.l.Unlock()}()time.Sleep(time.Second*1)go func() {lockTest.l.RLock()//阻塞等待写锁释放fmt.Println("read begin")fmt.Println("read data:",lockTest.data)fmt.Println("read begin")lockTest.l.RUnlock()}()time.Sleep(time.Second*5)}结果:write data beginwrite data endread beginread data: new dataread begin
  • sync.Cond(golang 内置方法) , 用于条件变量
sync.Cond用于条件等待 , 在满足某些条件时程序才能继续执行 。 它包含如下3个方法:Wait()会挂起其所在的协程等待Signal()或Broadcast()的唤醒 。
func (c *Cond) Wait() func (c *Cond) Signal()func (c *Cond) Broadcast() 官方推荐的典型用法如下 。 由于唤醒协程并不意味着条件已就绪 , 因此在唤醒后需要检测是否本协程的条件已经满足 。
c.L.Lock()for !condition() {c.Wait()}... make use of condition ...c.L.Unlock()使用Signal()唤醒的方式如下 , Signal()用于当次唤醒一个协程 。 如果注释掉下例中的Signal() , 那么两个协程会一直Wait() , 并不会继续执行 。
package mainimport ("fmt""sync")func main(){l := sync.Mutex{}c := sync.NewCond( i < 10; i++ {go func(i int) {defer wg.Done()fmt.Print(i, " ")}(i)}wg.Wait()}结果:9 4 0 1 2 3 6 5 7 8
  • 协程间使用chan进行同步
下例中使用chan实现主协程控制write , 并使用write控制read 。 协程关闭使用close()函数
ps:使用chan进行协程同步一般将chan作为入参传入 , 或在函数内部实现协程间的同步 。 为方便验证 , 下面例子将所有chan作为全局变量