Go源码分析(4) - expvar

Go源码分析(4) - expvar

今天是要分析的是一个封装好的关于 int,string,float, map 等基本类型原子操作包,还有一些公共发布变量。

Int

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Int is a 64-bit integer variable that satisfies the Var interface.
type Int struct {
i int64
}

func (v *Int) Value() int64 {
return atomic.LoadInt64(&v.i)
}

func (v *Int) String() string {
return strconv.FormatInt(atomic.LoadInt64(&v.i), 10)
}

func (v *Int) Add(delta int64) {
atomic.AddInt64(&v.i, delta)
}

func (v *Int) Set(value int64) {
atomic.StoreInt64(&v.i, value)
}

以int64作为基本类型,然后使用atomic包下等原子操作。

Float

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Float is a 64-bit float variable that satisfies the Var interface.
type Float struct {
f uint64
}

func (v *Float) Value() float64 {
return math.Float64frombits(atomic.LoadUint64(&v.f))
}

func (v *Float) String() string {
return strconv.FormatFloat(
math.Float64frombits(atomic.LoadUint64(&v.f)), 'g', -1, 64)
}

// Add adds delta to v.
func (v *Float) Add(delta float64) {
for {
cur := atomic.LoadUint64(&v.f)
curVal := math.Float64frombits(cur)
nxtVal := curVal + delta
nxt := math.Float64bits(nxtVal)
if atomic.CompareAndSwapUint64(&v.f, cur, nxt) {
return
}
}
}

// Set sets v to value.
func (v *Float) Set(value float64) {
atomic.StoreUint64(&v.f, math.Float64bits(value))
}

以uint64作为基本类型,来实现float类型等原子操作。

  • Add(delta float64)使用cas算法不断拿到最新值,重复进行cas设置,直到成功为止。
1
2
3
4
5
6
7
8
9
10
// Float64bits returns the IEEE 754 binary representation of f,
// with the sign bit of f and the result in the same bit position,
// and Float64bits(Float64frombits(x)) == x.
func Float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) }

// Float64frombits returns the floating-point number corresponding
// to the IEEE 754 binary representation b, with the sign bit of b
// and the result in the same bit position.
// Float64frombits(Float64bits(x)) == x.
func Float64frombits(b uint64) float64 { return *(*float64)(unsafe.Pointer(&b)) }

在其中使用到了func Float64bits(f float64) uint64将float类型转换为uint64类型进行存储,使用Float64frombits(b uint64) float64将uint64类型转换为float类型。

1
2
3
4
5
// LoadUint64 atomically loads *addr.
func LoadUint64(addr *uint64) (val uint64)

// StoreUint64 atomically stores val into *addr.
func StoreUint64(addr *uint64, val uint64)

转换为uint64类型,使用atomic包下到存储和去出uint64类型。

Map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Var is an abstract type for all exported variables.
type Var interface {
// String returns a valid JSON value for the variable.
// Types with String methods that do not return valid JSON
// (such as time.Time) must not be used as a Var.
String() string
}

// Map is a string-to-Var map variable that satisfies the Var interface.
type Map struct {
m sync.Map // map[string]Var
keysMu sync.RWMutex
keys []string // sorted
}

从定义上很清楚的知道Var是一个抽象接口,String()方法返回的是一个json字符串
Map类型是一个string类型作为key, 实现了Var接口类型的值作为value的键值对,结构体如下:

1
2
3
4
5
// KeyValue represents a single entry in a Map.
type KeyValue struct {
Key string
Value Var
}

Map本身也是一个实现了Var接口对类型,其,String()方法会返回Map内的全部键值对作为json字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func (v *Map) String() string {
var b strings.Builder
fmt.Fprintf(&b, "{")
first := true
v.Do(func(kv KeyValue) {
if !first {
fmt.Fprintf(&b, ", ")
}
fmt.Fprintf(&b, "%q: %v", kv.Key, kv.Value)
first = false
})
fmt.Fprintf(&b, "}")
return b.String()
}

可以看到其中调用Map本身的Do()方法,首先要对整个方法加读锁,这个Do()方法里面有一个循环遍历map的所有的键,然后通过key取出value,包装成KeyValue结构体,然后被传入f func(KeyValue)调用。

1
2
3
4
5
6
7
8
9
10
11
// Do calls f for each entry in the map.
// The map is locked during the iteration,
// but existing entries may be concurrently updated.
func (v *Map) Do(f func(KeyValue)) {
v.keysMu.RLock()
defer v.keysMu.RUnlock()
for _, k := range v.keys {
i, _ := v.m.Load(k)
f(KeyValue{k, i.(Var)})
}
}

接下来我看看添加key的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
// addKey updates the sorted list of keys in v.keys.
func (v *Map) addKey(key string) {
v.keysMu.Lock()
defer v.keysMu.Unlock()
// Using insertion sort to place key into the already-sorted v.keys.
if i := sort.SearchStrings(v.keys, key); i >= len(v.keys) {
v.keys = append(v.keys, key)
} else if v.keys[i] != key {
v.keys = append(v.keys, "")
copy(v.keys[i+1:], v.keys[i:])
v.keys[i] = key
}
}

首先加了一个读锁,使用插入排序将键放入已排序的v.keys中。通过key在keys中找到要插入的合适的位置,然后将keys的长度加1,将i - n-1位置的值移动到i+1 - n上,然后将keys[i]设置为key。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func (v *Map) Get(key string) Var {
i, _ := v.m.Load(key)
av, _ := i.(Var)
return av
}

func (v *Map) Set(key string, av Var) {
// Before we store the value, check to see whether the key is new. Try a Load
// before LoadOrStore: LoadOrStore causes the key interface to escape even on
// the Load path.
if _, ok := v.m.Load(key); !ok {
if _, dup := v.m.LoadOrStore(key, av); !dup {
v.addKey(key)
return
}
}

v.m.Store(key, av)
}

这两个方法很简单,一个是取值,通过sync.map的Load方法,一个是设置值,但是这里除了用sync.map的LoadOrStore方法,还需要将key加入到keys中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Add adds delta to the *Int value stored under the given map key.
func (v *Map) Add(key string, delta int64) {
i, ok := v.m.Load(key)
if !ok {
var dup bool
i, dup = v.m.LoadOrStore(key, new(Int))
if !dup {
v.addKey(key)
}
}

// Add to Int; ignore otherwise.
if iv, ok := i.(*Int); ok {
iv.Add(delta)
}
}


// AddFloat adds delta to the *Float value stored under the given map key.
func (v *Map) AddFloat(key string, delta float64) {
i, ok := v.m.Load(key)
if !ok {
var dup bool
i, dup = v.m.LoadOrStore(key, new(Float))
if !dup {
v.addKey(key)
}
}

// Add to Float; ignore otherwise.
if iv, ok := i.(*Float); ok {
iv.Add(delta)
}
}

两个都是添加的方法,一个是value为Int的方法,一个是value为Float的方法,先找到这个是否有这个key,如果没有就创建这个key,设置value为一个默认值,然后将累加值再后面进行相加(如果没有这个值的话,默认值就是0)。

1
2
3
4
5
6
7
8
9
10
// Deletes the given key from the map.
func (v *Map) Delete(key string) {
v.keysMu.Lock()
defer v.keysMu.Unlock()
i := sort.SearchStrings(v.keys, key)
if i < len(v.keys) && key == v.keys[i] {
v.keys = append(v.keys[:i], v.keys[i+1:]...)
v.m.Delete(key)
}
}

删除这个key,首先也是用过插入排序的方法吗,该值在目标索引的位置,然后通过切片的方法,重新拼装一个keys,然后再从sync.map中删除这个keyvalue

String

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// String is a string variable, and satisfies the Var interface.
type String struct {
s atomic.Value // string
}

func (v *String) Value() string {
p, _ := v.s.Load().(string)
return p
}

// String implements the Var interface. To get the unquoted string
// use Value.
func (v *String) String() string {
s := v.Value()
b, _ := json.Marshal(s)
return string(b)
}

func (v *String) Set(value string) {
v.s.Store(value)
}

这个String结构体也很简单,同样也是实现了Var接口,每一个操作都是原子操作

Func

1
2
3
4
5
6
7
8
9
10
11
12
// Func implements Var by calling the function
// and formatting the returned value using JSON.
type Func func() interface{}

func (f Func) Value() interface{} {
return f()
}

func (f Func) String() string {
v, _ := json.Marshal(f())
return string(v)
}

Func通过调用函数并使用JSON格式化返回值来实现Var。类型Func Func()接口{}

使用方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func TestFunc(t *testing.T) {
RemoveAll()
var x interface{} = []string{"a", "b"}
f := Func(func() interface{} { return x })
if s, exp := f.String(),`["a","b"]`; s != exp {
t.Errorf(`f.String() = %q, want %q`, s, exp)
}
if v := f.Value(); !reflect.DeepEqual(v, x) {
t.Errorf(`f.Value() = %q, want %q`, v, x)
}

x = 17
if s, exp := f.String(),`17`; s != exp {
t.Errorf(`f.String() = %q, want %q`, s, exp)
}
}

All published variables

下面是所有所有已发布的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// All published variables.
var (
vars sync.Map // map[string]Var
varKeysMu sync.RWMutex
varKeys []string // sorted
)

// Publish declares a named exported variable. This should be called from a
// package's init function when it creates its Vars. If the name is already
// registered then this will log.Panic.
func Publish(name string, v Var) {
if _, dup := vars.LoadOrStore(name, v); dup {
log.Panicln("Reuse of exported var name:", name)
}
varKeysMu.Lock()
defer varKeysMu.Unlock()
varKeys = append(varKeys, name)
sort.Strings(varKeys)
}

// Get retrieves a named exported variable. It returns nil if the name has
// not been registered.
func Get(name string) Var {
i, _ := vars.Load(name)
v, _ := i.(Var)
return v
}

这一部分代码的作用就是一些公共遍历被存储的地方,vars是一个存储keyvalue的map,varKeysMu则是一个读写锁,varKeys则是一个keys的集合,这个和Map的数据类型很像。

  • Publish(name string, v Var) Publish声明一个命名的导出变量。当包创建变量时,应该从包的init函数调用它。如果名称已经注册,则这将导致log.Panic。
  • Get(name string) VarGet检索命名的导出变量。如果名称尚未注册,则返回nil。

下面是创建新导出变量的工具函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Convenience functions for creating new exported variables.

func NewInt(name string) *Int {
v := new(Int)
Publish(name, v)
return v
}

func NewFloat(name string) *Float {
v := new(Float)
Publish(name, v)
return v
}

func NewMap(name string) *Map {
v := new(Map).Init()
Publish(name, v)
return v
}

func NewString(name string) *String {
v := new(String)
Publish(name, v)
return v
}

Do(f func(KeyValue))方法为每个导出的变量调用f。全局变量映射在迭代期间被锁定,但是现有的条目可以同时更新。

1
2
3
4
5
6
7
8
9
10
11
// Do calls f for each exported variable.
// The global variable map is locked during the iteration,
// but existing entries may be concurrently updated.
func Do(f func(KeyValue)) {
varKeysMu.RLock()
defer varKeysMu.RUnlock()
for _, k := range varKeys {
val, _ := vars.Load(k)
f(KeyValue{k, val.(Var)})
}
}

expvarHandler(w http.ResponseWriter, r *http.Request),通过http服务器,查看当前所有的公共发布变量,同样保存了机器的命令行启动参数以及内存状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
func expvarHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
fmt.Fprintf(w, "{\n")
first := true
Do(func(kv KeyValue) {
if !first {
fmt.Fprintf(w, ",\n")
}
first = false
fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
})
fmt.Fprintf(w, "\n}\n")
}

// Handler returns the expvar HTTP Handler.
//
// This is only needed to install the handler in a non-standard location.
func Handler() http.Handler {
return http.HandlerFunc(expvarHandler)
}

func cmdline() interface{} {
return os.Args
}

func memstats() interface{} {
stats := new(runtime.MemStats)
runtime.ReadMemStats(stats)
return *stats
}

func init() {
http.HandleFunc("/debug/vars", expvarHandler)
Publish("cmdline", Func(cmdline))
Publish("memstats", Func(memstats))
}

init()会初始化的将公共变量发布到http服务器,如果服务器开启的话就可以访问到这些信息 。

总结

个人认为expvar是一个很有用的工具包,可以替代繁琐的atomic包下的内容,同时map也是,并且String()方法返回的json字符串也是很有用的。

评论

`
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×