GO指南

引言

Go

Go语言是谷歌2009发布的编程语言,它是一种并发的、带垃圾回收的、快速编译的语言。

它结合了解释型语言的游刃有余,动态类型语言的开发效率,以及静态类型的安全性。它也打算成为现代的,支持网络与多核计算的语言。要满足这些目标,需要解决一些语言上的问题:一个富有表达能力但轻量级的类型系统,并发与垃圾回收机制,严格的依赖规范等等。这些无法通过库或工具解决好,因此Go也就应运而生了。

Gopher

优势:

  1. 部署简单
    Go 编译生成的是一个静态可执行文件,除了 glibc 外没有其他外部依赖。这让部署变得异常方便:目标机器上只需要一个基础的系统和必要的管理、监控工具,完全不需要操心应用所需的各种包、库的依赖关系,大大减轻了维护的负担。可直接编译成机器码,不依赖其他库,glibc的版本有一定要求,部署就是扔一个文件上去就完成了。

  2. 并发性好
    Goroutine 和 channel 使得编写高并发的服务端软件变得相当容易, 很多情况下完全不需要考虑锁机制以及由此带来的各种问题。 单个 Go 应用也能有效的利用多个 CPU 核,并行执行的性能好。

  3. 良好的语言设计
    从工程的角度讲,Go 的设计是非常优秀的:规范足够简单灵活,有其他语言基础的程序员都能迅速上手。更重要的是 Go 自带完善的工具链, 大大提高了团队协作的一致性。 比如 gofmt 自动排版 Go 代码, 保证不同人的代码排版风格一致。 此外还有 gofix, govet 等非常有用的工具。

  4. 丰富的标准库
    Go目前已经内置了大量的库,特别是网络库非常强大。

  5. 执行性能好
    虽然不如 C 和 Java,但通常比原生 Python 应用还是高一个数量级的,适合编写一些瓶颈业务。内存占用也非常省。

适用场景

  • 服务器编程,以前你如果使用C或者C++做的那些事情,用Go来做很合适,例如:处理日志、数据打包、虚拟机处理、文件系统等;

  • 分布式系统,数据库代理器等;

  • 内存数据库,前一段时间google开发的groupcache,couchbase的部分组建;

  • 云平台,目前国外很多云平台在采用Go开发,CloudFoundy的部分组建,前VMare的技术总监自己出来搞的apcera云平台。

成功项目

nsq:bitly开源的消息队列系统,性能非常高,目前他们每天处理数十亿条的消息
docker:基于lxc的一个虚拟打包工具,能够实现PAAS平台的组建。
packer:用来生成不同平台的镜像文件,例如VM、vbox、AWS等,作者是vagrant的作者
skynet:分布式调度框架
Doozer:分布式同步工具,类似ZooKeeper
Heka:mazila开源的日志处理系统
cbfs:couchbase开源的分布式文件系统
tsuru:开源的PAAS平台,和SAE实现的功能一模一样
groupcache:memcahe作者写的用于Google下载系统的缓存系统
god:类似redis的缓存系统,但是支持分布式和扩展性
gor:网络流量抓包和重放工具

缺点:

  1. Go的import包不支持版本,有时候升级容易导致项目不可运行,需要自己控制相应的版本信息

  2. Go的goroutine一旦启动之后,不同的goroutine之间切换不是受程序控制,runtime调度的时候,需要严谨的逻辑,不然goroutine休眠,过一段时间逻辑结束了,突然冒出来又执行了,会导致逻辑出错等情况。

  3. GC延迟有点大,并发很大的情况下,处理很大的日志,GC 没有那么快,内存回收不给力,后来经过 profile 程序改进之后得到了改善。

为何没流行?

  • 没什么太多应用场景非要 Golang 才能做的 ( Golang 最大特点是异步和单进程跑多核 )
  • 开发 web 没有 php ruby 成熟、快速、人好招
  • 开发 server 没有 java 现成解决方案多

GO指南

环境搭建

安装Golang的SDK

(1) http://www.golangtc.com/download
(2) 安装完成之后,打开终端,输入go、或者go version查看安装版本

配置Go环境变量GOPATH和GOBIN

  (1)打开终端,cd ~

  (2)查看是否有.bash_profile文件:

     ls -all

  (3)有则跳过此步,没有则:

    1)创建:touch .bash_profile

    2)编辑:open -e .bash_profile

    3)自定义GOPATH和GOBIN位置:

        GOPATH:日常开发的根目录。GOBIN:是GOPATH下的bin目录。
        export GOPATH=/Users/yuan/go
        export GOBIN=$GOPATH/bin
        export PATH=$PATH:$GOBIN

  (4)编译:source .bash_profile

  (5)*查看Go环境变量:go env
  

开发工具配置

sublime text

一定要先配置好Go环境变量GOPATH和GOBIN,再安装此插件,要不插件会提示找不到GOPATH和GOBIN;

选用 sublime text 安装 gosublime 插件进行开发( golang 语法高亮提示)

  (1)安装 package controll(若已安装,请跳过)
  使用Ctrl+`快捷键或者通过View->Show Console菜单打开命令行,粘贴如下代码:

import urllib.request,os; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); open(os.path.join(ipp, pf), 'wb').write(urllib.request.urlopen( 'http://sublime.wbond.net/' + pf.replace(' ','%20')).read()) 2

  (2)install go sublime
Command+shift+p 输入并选择packageControl: install package
然后输入并选择goSublime
安装完成就OK啦~~

Gogland
选择Gogland, 下载安装即可,3个月
    https://www.jetbrains.com/go/download/
LiteIDE
    国产IDE    
    http://golangtc.com/download/liteide

小试牛刀

在你的gopath目录下,新建main.go文件即可以进行编码了。
package main
import (
    "fmt"
)
func main() {
    fmt.Println("hello gopher~");
}

代码编写完成之后,使用command+b打开sublime text终端

(一)编译+执行
使用go build main.go对其进行编译,编译通过的结果信息如下:

[ go build main.go | done: 420.495985ms ]
提示编译成功之后,再执行shell命令,执行刚刚编译之后的文件./main即可看到运行结果:

[ ./main | done: 10.532868ms ]
hello go

(二)直接执行
如果仅仅是只需要看到运行的结果,而不产生可执行文件(文件名和项目名一样)则在sublime text终端中直接使用go run xxx.go即可:

[ go run main.go | done: 314.476988ms ]
hello go

基础

包、变量、函数

package main

import "fmt"

func main() {
    var hello string = "Hello"
    who := "gopher"
    var s = hello+"," + who
    fmt.Println(s)
}
  1. 每个 Go 程序都是由包组成的。
  2. 程序运行的入口是包 main 。
  3. 按照惯例,包名与导入路径的最后一个目录一致。例如,”math/rand” 包由 package rand 语句开始。
变量
  • 变量声明使用关键字var
  • 初始值存在时可省略类型声明
  • 短赋值语句:= 可以用于替代 var 的隐式类型声明(:=结构不能使用在函数外,函数外的每个语法块都必须以关键字开始)
var name1 string //声明变量
name1 = "tom" //给变量赋值
var name2 string = "tom" //声明变量+赋值
var name3 = "tom" // 声明时同时赋值,可以省略变量类型
name4 := "tom" //短赋值语句

// 多个变量
var x, y, z int
var c, python, java bool

var x, y, z int = 1, 2, 3
var c, python, java = true, false, "no!"

c, python, java := true, false, "no!"
函数
  • 可以返回任意数量的返回值
  • 类型声明在变量名之后
  • 同一类型的多个参数,最后一个参数需声明类型
func swap(x, y string) (string, string) {
    return y, x
}
  • 命名返回值的参数
func split(sum int) (x, y int) {
    x = sum * 4/9
    y = sum - x
    return
}
基本类型
bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // uint8 的别名

rune // int32 的别名
     // 代表一个Unicode码位

float32 float64

complex64 complex128
常量
const Pi = 3.14
const World = "世界"
const Truth = true
运算符

http://www.yiibai.com/go/go_operators.html

注意:

  • 没有++i,–i,只有i++、i–
  • 不能使用i++、i–对变量直接赋值

流程控制

for

Go 只有一种循环结构—— for 循环

for i := 0; i < 10; i++ {
    //do something
}

i := 0
for ; i < 1000; {
    //do something
}
for i < 1000 {
    //do something
} 
for {
    //死循环
}
if else
if x < 0 {
    return x
}

if v := 0; v < 5 {
    return v
}
return 9
switch

case 语句匹配后会自动终止(无需break),除非用 fallthrough 语句作为结尾,则会强制执行下一条case的语句或者default语句,而不判断expression。

    x := 2
    switch x {
    case 1:
        fmt.Println(1)
    case 2:
        fmt.Println(2)
        fallthrough
    case 3:
        fmt.Println(x > 1)
    default:
        fmt.Println("default")
    }

// 结果
// 2
// true
defer

延迟(defer)处理
Defer用于确保在稍后的程序中,执行函数调用。
defer语句在封装函数(main)结束时执行。

package main

import "fmt"
import "os"

func main() {
    f := createFile("defer-test.txt")
    defer closeFile(f)
    writeFile(f)
}

func createFile(p string) *os.File {
    fmt.Println("creating")
    f, err := os.Create(p)
    if err != nil {
        panic(err)
    }
    return f
}

func writeFile(f *os.File) {
    fmt.Println("writing")
    fmt.Fprintln(f, "data")
}

func closeFile(f *os.File) {
    fmt.Println("closing")
    f.Close()
}


// output

  creating
  writing
  closing

复杂类型

struct

要定义结构,必须使用type和struct语句。struct语句定义了一个新的数据类型,在程序中有多个成员。type语句在例子中绑定一个类型为struct的名字。 struct语句的格式如下:

type person struct {
    name string
    age  int
}

访问结构体成员使 .

package main

import "fmt"


type person struct {
    name string
    age  int
}

func main() {

    fmt.Println(person{"Bob", 20})
    fmt.Println(person{name: "Alice", age: 30})
    fmt.Println(person{name: "Fred"})
    fmt.Println(&person{name: "Ann", age: 40})
    s := person{name: "Sean", age: 50}
    fmt.Println(s.name)
    sp := &s
    fmt.Println(sp.age)
    sp.age = 51
    fmt.Println(sp.age)
}

#output
{Bob 20}
{Alice 30}
{Fred 0}
&{Ann 40}
Sean
50
51
slice

因为切片(Slice)是数组上的抽象。 它实际上使用数组作为底层结构体.len()函数返回切片中存在的元素数量,其中cap()函数返回切片(Slice)的容量(大小),即可容纳多少个元素。

package main

import "fmt"

func main() {
   var numbers = make([]int,3,5)

   printSlice(numbers)
}

func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

//output
len=3 cap=5 slice=[0 0 0]
map

var map_variable map[key_data_type]value_data_type
map_variable = make(map[key_data_type]value_data_type)

map_variable := make(map[key_data_type]value_data_type)

delete(map_variable, key)

package main

import "fmt"

func main() {
    var countryCapitalMap map[string]string = make(map[string]string)
    /* create a map*/
    // countryCapitalMap = make(map[string]string)

    /* insert key-value pairs in the map*/
    countryCapitalMap["France"] = "Paris"
    countryCapitalMap["Italy"] = "Rome"
    countryCapitalMap["Japan"] = "Tokyo"
    countryCapitalMap["India"] = "New Delhi"

    /* print map using keys*/
    for country := range countryCapitalMap {
        fmt.Println("Capital of", country, "is", countryCapitalMap[country])
    }

    /* test if entry is present in the map or not*/
    capital, ok := countryCapitalMap["United States"]
    /* if ok is true, entry is present otherwise entry is absent*/
    if ok {
        fmt.Println("Capital of United States is", capital)
    } else {
        fmt.Println("Capital of United States is not present")
    }
    /* delete an entry */
    delete(countryCapitalMap, "France")
    fmt.Println("Entry for France is deleted")

    fmt.Println("Updated map")

    /* print map */
    for country := range countryCapitalMap {
        fmt.Println("Capital of", country, "is", countryCapitalMap[country])
    }
}
range
package main

import "fmt"

func main() {

    nums := []int{2, 3, 4}
    sum := 0
    for _, num := range nums {
        sum += num
    }
    fmt.Println("sum:", sum)

    for i, num := range nums {
        if num == 3 {
            fmt.Println("index:", i)
        }
    }

    kvs := map[string]string{"a": "apple", "b": "banana"}
    for k, v := range kvs {
        fmt.Printf("%s -> %s\n", k, v)
    }

    for k := range kvs {
        fmt.Println("key:", k)
    }

}

方法和接口

方法

package main

import "fmt"

type rect struct {
    width, height int
}

func (r *rect) area() int {
    return r.width * r.height
}

func (r rect) perim() int {
    return 2*r.width + 2*r.height
}
func main() {
    r := rect{width: 10, height: 5}

    fmt.Println("area: ", r.area())
    fmt.Println("perim:", r.perim())

    rp := &r
    fmt.Println("area: ", rp.area())
    fmt.Println("perim:", rp.perim())
}

// output
  area:  50
  perim: 30
  area:  50
  perim: 30

接口

package main

import "fmt"
import "math"

type geometry interface {
    area() float64
    perim() float64
}

type rect struct {
    width, height float64
}
type circle struct {
    radius float64
}

func (r rect) area() float64 {
    return r.width * r.height
}
func (r rect) perim() float64 {
    return 2*r.width + 2*r.height
}

func (c circle) area() float64 {
    return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
    return 2 * math.Pi * c.radius
}

func measure(g geometry) {
    fmt.Println(g)
    fmt.Println(g.area())
    fmt.Println(g.perim())
}
func main() {
    r := rect{width: 3, height: 4}
    c := circle{radius: 5}

    measure(r)
    measure(c)
}

// output
  {3 4}
  12
  14
  {5}
  78.53981633974483
  31.41592653589793

并发

goroutine

package main
import "fmt"
func f(from string) {
    for i := 0; i < 3; i++ {
        fmt.Println(from, ":", i)
    }
}

func main() {

    f("direct")
    go f("goroutine")

    go func(msg string) {
        fmt.Println(msg)
    }("going")

    var input string
    fmt.Scanln(&input)
    fmt.Println("done")
}

// output
$ go run goroutines.go
direct : 0
direct : 1
direct : 2
goroutine : 0
going
goroutine : 1
goroutine : 2
<enter>
done

channel

channel 是有类型的管道,可以用 channel 操作符 <- 对其发送或者接收值。

ch <- v // 将 v 送入 channel ch。
v := <-ch // 从 ch 接收,并且赋值给 v。

默认情况下,在另一端准备好之前,发送和接收都会阻塞。这使得 goroutine 可以在没有明确的锁或竞态变量的情况下进行同步。

package main
import "fmt"
func main() {

    messages := make(chan string)

    go func() { messages <- "ping" }()

    msg := <-messages
    fmt.Println(msg)
}

channel 可以是带缓冲的。为 make 提供第二个参数作为缓冲长度来初始化一个缓冲 channel:

ch := make(chan int, 100)
向缓冲 channel 发送数据的时候,只有在缓冲区满的时候才会阻塞。当缓冲区清空的时候接受阻塞。

package main

import "fmt"

func main() {
    c := make(chan int, 2)
    c <- 1
    c <- 2
    fmt.Println(<-c)
    fmt.Println(<-c)
}

close

发送者可以 close 一个 channel 来表示再没有值会被发送了。接收者可以通过赋值语句的第二参数来测试 channel 是否被关闭:当没有值可以接收并且 channel 已经被关闭,那么

v, ok := <-ch
ok 会被设置为 false。

注意: 只有发送者才能关闭 channel,而不是接收者。向一个已经关闭的 channel 发送数据会引起 panic。

package main

import "fmt"

func main() {
    jobs := make(chan int, 5)
    done := make(chan bool)

    go func() {
        for {
            j, more := <-jobs
            if more {
                fmt.Println("received job", j)
            } else {
                fmt.Println("received all jobs")
                done <- true
                return
            }
        }
    }()

    for j := 1; j <= 3; j++ {
        jobs <- j
        fmt.Println("sent job", j)
    }
    close(jobs)

    fmt.Println("sent all jobs")

    <-done
}

select

Go语言的选择(select)可等待多个通道操作。将goroutine和channel与select结合是Go语言的一个强大功能。

select 会阻塞,直到条件分支中的某个可以继续执行,这时就会执行那个条件分支。如果有多个都准备好的时候,会随机选一个。

package main

import "time"
import "fmt"

func main() {
    c1 := make(chan string)
    c2 := make(chan string)
    c3 := make(chan string)

    t1 := time.Now().UnixNano()

    go func() {
        time.Sleep(time.Second * 1)
        c1 <- "one"
    }()
    go func() {
        time.Sleep(time.Second * 2)
        c2 <- "two"
    }()

    go func() {
        time.Sleep(time.Second * 2)
        c3 <- "three"
    }()

    for i := 0; i < 3; i++ {
        select {
        case msg1 := <-c1:
            fmt.Println("received", msg1)
        case msg2 := <-c2:
            fmt.Println("received", msg2)
        case msg3 := <-c3:
            fmt.Println("received", msg3)
        }
    }
    t2 := time.Now().UnixNano()
    dt := (t2 - t1) / 1e6
    fmt.Println(dt)

}

实战

函数闭包

函数是完全闭包的。

package main

import "fmt"
// 函数 adder 返回一个闭包。每个闭包被绑定到自己的 sum 变量上
func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}

func main() {
    pos, neg := adder(), adder()
    for i := 0; i < 10; i++ {
        fmt.Println(
            pos(i),
            neg(-2*i),
        )
    }
}
error

Go编程提供了一个非常简单的错误处理框架,以及内置的错误接口类型,如下声明:

type error interface {
   Error() string
}

Go函数通常返回错误作为最后一个返回值。 可使用errors.New来构造一个基本的错误消息

package main

import "errors"
import "fmt"
import "math"

func Sqrt(value float64)(float64, error) {
   if(value < 0){
      return 0, errors.New("Math: negative number passed to Sqrt")
   }
   return math.Sqrt(value), nil
}

func main() {
   result, err:= Sqrt(-1)

   if err != nil {
      fmt.Println(err)
   }else {
      fmt.Println(result)
   }

   result, err = Sqrt(9)

   if err != nil {
      fmt.Println(err)
   }else {
      fmt.Println(result)
   }
}

开发陷阱

开大括号不能放在单独的一行
未使用的变量
未使用的Imports
不支持前置版本的自增和自减,也无法在表达式中使用这两个操作符。
。。。

50坑
http://studygolang.com/articles/8382


Reference:
1. http://gotour.qizhanming.com/#1
2. https://tour.go-zh.org/basics/1
3. http://www.yiibai.com/go/go_start.html
4. https://www.zhihu.com/question/21409296
5. https://gobyexample.com/

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值