go语言的成功是典型实用主义思维的成功。go语言就像是一个面向过程(C),面向对象(JAVA)及函数编程(javascript)的揉合体,充分消化吸取这些编程思想中的最实用点。在go中,我们可以看到,C的结构体和指针,JAVA的接口,及函数编程中函数是一等公民等等特性。下面我们就来浅析一下这些有意思的地方。
指针的运用。
C/C++中,最容易出问题,最令人诟病的就是指针。当初在JAVA的设计中为了解决这个问题,就彻底抛弃了指针。虽然go保留了指针,但是go编译器帮助做了优化, 使用起来更加自然。
- 编译器帮你进行指针类型的隐式转换
如下例:通过指针employeeOfTheMonth和操作符. 就像Java一样很自然的设置Position
var employeeOfTheMonth *Employee = &dilbert |
- 调用对象方法时,也一样。
pptr.Distance(q) 等价于 (*pptr).Distance(q) |
- 操作指针时,也只是更新内容而不是指针本身。这样就减少了C/C++中使用指针带来的内存问题。
func incr(p *int) int { |
函数是一等公民
函数作为一等公民可以赋值,也可以作为函数的输入输出值。
func square(n int) int { return n * n } |
结构体Tag。
JSON对象几乎是WEB开发中必不可少的,go通过结构体tag可以很自然的在结构体和JSON对象间进行自如转换。
例如:下面的代码就通过json.Marshal使得Year转换成JSON对象时的released。另外,如果希望输出的格式有缩进,可以使用json.MarshalInden来代替json.Marshal。// 定义结构体 及 在Year,Color上加Tag
type Movie struct {
Title string
Year int `json:"released"`
Color bool `json:"color,omitempty"`
Actors []string
}
// 初始化 movies
var movies = []Movie{
{Title: "Casablanca", Year: 1942, Color: false,
Actors: []string{"Humphrey Bogart", "Ingrid Bergman"}},
{Title: "Cool Hand Luke", Year: 1967, Color: true,
Actors: []string{"Paul Newman"}},
{Title: "Bullitt", Year: 1968, Color: true,
Actors: []string{"Steve McQueen", "Jacqueline Bisset"}},
// ...
}
// 转换成json
data, err := json.Marshal(movies)
if err != nil {
log.Fatalf("JSON marshaling failed: %s", err)
}
fmt.Printf("%s\n", data)
// 输出结果
[{"Title":"Casablanca","released":1942,"Actors":["Humphrey Bogart","Ingr
id Bergman"]},{"Title":"Cool Hand Luke","released":1967,"color":true,"Ac
tors":["Paul Newman"]},{"Title":"Bullitt","released":1968,"color":true,"
Actors":["Steve McQueen","Jacqueline Bisset"]}]
模版文件使得格式化输出更加丰富
go中支持text/template和html/template等模版文件,下面的代码就是以text/template为例,来看看如何使用template。
定义模版文件
const templ = `{{.TotalCount}} issues:
{{range .Items}}----------------------------------------
Number: {{.Number}}
User: {{.User.Login}}
Title: {{.Title | printf "%.64s"}}
Age: {{.CreatedAt | daysAgo}} days
{{end}}`解析模版文件,创建模版对象并注册模版文件中要使用的函数
// 定义函数
func daysAgo(t time.Time) int {
return int(time.Since(t).Hours() / 24)
}
// call .New .Funcs .Parse
report, err := template.New("report").
Funcs(template.FuncMap{"daysAgo": daysAgo}).
Parse(templ)
if err != nil {
log.Fatal(err)
}调用模版对象,进行输出。
var report = template.Must(template.New("issuelist").
Funcs(template.FuncMap{"daysAgo": daysAgo}).
Parse(templ))
func main() {
result, err := github.SearchIssues(os.Args[1:])
if err != nil {
log.Fatal(err)
}
if err := report.Execute(os.Stdout, result); err != nil {
log.Fatal(err)
}
}
关注于数据结构及其行为。
go和Java等面向对象编程语言相比,在面向对象方面进行了简化。 例如:没有访问级别, 没有继承(通过组合来实现继承);go重点关注于数据结构本事及其行为(这里指方法和实现的接口)。
//通过组合来实现继承的例子 |
defer关键字
defer关键字确保了资源的释放,相当于Java中的finally关键字。
func Balance() int { |
Goroutine和CSP并发模型。
下面是一个使用goroutine和channel的例子:数据经过channel就像pipeline一样被不同的goroutine处理。# counter -> squarter -> printer
func counter(out chan<- int) { |
goroutine 是Go语言中并发的执行单位。从OS的角度来看真正工作的是线程,goroutine和线程的关系如下:
一个M会对应一个内核线程,一个M也会连接一个上下文P,一个上下文P相当于一个“处理器”,一个上下文连接一个或者多个Goroutine。P(Processor)的数量是在启动时被设置为环境变量GOMAXPROCS的值,或者通过运行时调用函数runtime.GOMAXPROCS()进行设置。Processor数量固定意味着任意时刻只有固定数量的线程在运行go代码。Goroutine中就是我们要执行并发的代码。图中P正在执行的Goroutine为蓝色的;处于待执行状态的Goroutine为灰色的,灰色的Goroutine形成了一个队列runqueues
三者关系的宏观的图为:
go语言在os用户空间中设计了goroutine,通过dynamice stack(一个goroutine 2k-1G) 及上图的 m:n scheduler成功在多核处理器上实现了大量的并发。
相比较actor模型的并发可以扩展到多个机器上,go是如何支持扩展到多个机器上的呢, channel over rpc, message queue?
附1,其它语言在协程上的支持如下:
附2,Actor和CSP模型:
性能优化
go注重性能优化,测试框架中自带了Benchmark测试和性能profile报告.
import "testing" |