Golang 公共变量包——expvar

栏目: Go · 发布时间: 7年前

内容简介:Golang 公共变量包——expvar

写在前面

expvar 包是 Golang 官方提供的公共变量包,它可以辅助调试全局变量。支持一些常见的类型: float64int64MapString 。如果我们的程序要用到上面提的四种类型(其中,Map 类型要求 Key 是字符串)。可以考虑使用这个包。

功能

  1. 它支持对变量的基本操作,修改、查询这些;
  2. 整形类型,可以用来做计数器;
  3. 操作都是线程安全的。这点很不错。相信大家都自己整过全局变量,除了变量还得整的锁,自己写确实挺麻烦的;
  4. 此外还提供了调试接口, /debug/vars 。它能够展示所有通过这个包创建的变量;
  5. 所有的变量都是 Var 类型,可以自己通过实现这个接口扩展其它的类型;

    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 }

  6. Handler() 方法可以得到调试接口的 http.Handler ,和自己的路由对接。

这些基础的功能就不多说了,大家可以直接看官方的 文档

调试接口

看源码的时候发现一个非常有意思的调试接口, /debug/vars 会把所有注册的变量打印到接口里面。这个接口很有情怀。

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

源码

var (
	mutex   sync.RWMutex
	vars    = make(map[string]Var)
	varKeys []string // sorted
)
  1. varKeys 是全局变量所有的变量名,而且是有序的;
  2. vars 根据变量名保存了对应的数据。当然 mutex 就是这个 Map 的锁;
  3. 这三个变量组合起来其实是一个有序线程安全哈希表的实现。

    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
     }
    	
     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)
     }
  4. 这个包里面的所有类型都实现了这个接口;
  5. 以 Int 类型举例。实现非常的简单,注意 AddSet 方法是线程安全的。别的类型实现也一样

    func Publish(name string, v Var) {
         mutex.Lock()
         defer mutex.Unlock()
         if _, existing := vars[name]; existing {
             log.Panicln("Reuse of exported var name:", name)
         }
         vars[name] = v
         varKeys = append(varKeys, name)
         sort.Strings(varKeys)
     }
    	
     func NewInt(name string) *Int {
         v := new(Int)
         Publish(name, v)
         return v
     }
  6. 将变量注册到一开始介绍的 varsvarKeys 里面;
  7. 注册时候也是线程安全的,所有的变量名在注册的最后排了个序;
  8. 创建对象的时候会自动注册。

    func Do(f func(KeyValue)) {
         mutex.RLock()
         defer mutex.RUnlock()
         for _, k := range varKeys {
             f(KeyValue{k, vars[k]})
         }
     }
    
     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")
     }
    
     func Handler() http.Handler {
         return http.HandlerFunc(expvarHandler)
     }
  9. Do 方法,利用一个闭包,按照 varKeys 的顺序遍历所有全局变量;
  10. expvarHandler 方法是 http.Handler 类型,将所有变量通过接口输出,里面通过 Do 方法,把所有变量遍历了一遍。挺巧妙;
  11. 通过 http.HandleFunc 方法把 expvarHandler 这个外部不可访问的方法对外,这个方法用于对接自己的路由;
  12. 输出数据的类型, fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value) ,可以发现,值输出的字符串,所以输出的内容是 String() 的结果。这里有一个技巧,虽然调用的字符串的方法,但是由于输出格式 %s 外面并没有引号,所有对于 JSON 来说,输出的内容是对象类型。相当于在 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 cmdline() interface{} {
         return os.Args
     }
  13. 这是一个非常有意思的写法,它可以把任何类型转换成 Var 类型;
  14. Func 定义的是函数,它的类型是 func() interface{}
  15. Func(cmdline) ,使用的地方需要看清楚,参数是 cmdline 而不是 cmdline() ,所以这个写法是类型转换。转换完之后 cmdline 方法就有了 String() 方法,在 String() 方法里又调用了 f() ,通过 JSON 编码输出。这个小技巧在前面提到的 http.HandleFunc 里面也有用到,Golang 的 程序员 对这个是真爱,咱们编码的时候也要多用用啊。

不足

感觉这个包还是针对简单变量,比如整形、字符串这种比较好用。

  1. 前面已经说了,Map 类型只支持 Key 是字符串的变量。其它类型还得自己扩展,扩展的话锁的问题还是得自己搞。而且 JSON 编码低版本不支持 Key 是整形类型的编码,也是个问题;
  2. Var 接口太简单,只有一个 String() 方法,基本上只能输出变量所有内容,别的东西都没办法控制,如果你的变量有10000个键值对,那么这个接口基本上属于不能用。多说一句,这是 Golang 设计的常见问题,比如日志包,输出的类型是 io.Writer ,而这个接口只支持一个方法 Write([]byte) ,想扩展日志包的功能很难,这也失去了抽象出来一个接口的意义。
  3. 路由里面还默认追加了启动参数和 MemStats 内存相关参数。我个人觉得后面这个不应该加,调用 runtime.ReadMemStats(stats) 会引起 Stop The World,总感觉不值当。

总结

看到就写了,并没有什么沉淀,写得挺乱的。这个包很简单,但是里面还是有些可以借鉴的编码和设计。新版本的 Golang 已经能解析整形为 Key 的哈希表了,这个包啥时候能跟上支持一下?


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

PHP Hacks

PHP Hacks

Jack Herrington D. / O'Reilly Media / 2005-12-19 / USD 29.95

Programmers love its flexibility and speed; designers love its accessibility and convenience. When it comes to creating web sites, the PHP scripting language is truly a red-hot property. In fact, PH......一起来看看 《PHP Hacks》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器