编译时一个将源代码翻译成低级语言的过程。编译过程比较慢,在设计Go时,编译速度是主要的设计目标之一。静态类型意味着变量必须指定一个类型,如整形,字符串,布尔,数组等,能够在声明变量时指定变量类型,大多数状况下,让编译器自动去推断变量类型。git
变量有一个肯定的生命周期。例如函数中定义的局部变量,当函数退出时变量就不存在了。语言的垃圾回收机制能够记录不在使用的变量,而后释放他们占用的内存。垃圾回收机制带来一些性能影响。github
go run命令会先编译而后再运行你的代码,会在一个临时目录下编译这段代码,而后执行,最后自动清除生成的临时文件。若是只是编译代码可使用go build。sql
第一种方式:编程
var power int power = 9000
第二种方式:数组
var power int = 9000
第三种方式:用于声明一个变量并给变量赋值,go能够推断变量类型,在第一次声明变量时,使用:=,此时肯定了变量类型。但随后对于此变量的赋值,使用=。缓存
power := 9000 gg := getPower() func getPower() int{ return 9001 }
第四种方式:go支持多个变量同事赋值安全
name, power := "Goku", 9000
函数支持多值返回架构
没有返回值:并发
func log(message string){ }
一个返回值:app
func add (a int, b int) int{ }
两个返回值: func power(name string)(int,bool){ }
多个返回值的场景使用比较多,若是只想得到返回值中的某个值,能够将另外一个返回赋值给_:
_, exists:=power("goku") if exists == false{ }
_是一个空白标识符,多用在返回值时没有真正的赋值,不管返回值是什么类型。
若是函数的参数都是相同的类型,能够简洁的定义:
func add(a,b int) int{ }
go 不像面向对象语言,没有对象和继承的概念。所以也没有不少面向对象的语言的特征好比多态和重载。 go提供告终构体,如:
type Sanya struct{ Name string Province int }
经过简单的方式建立一个结构体值类型:
goku := Sanya{ Name : "sanya", Province :23, }
注意上面结构体结尾的逗号是不能省的。 当不须要给结构体设置任何值甚至任何字段:
goku := Sanya{} goku := Sanya{Name:"sanya"} goku.Province = 23
也能够省略字段的名字:
goku := Sanya{"sanya",23}
大多数状况,咱们不但愿变量直接关联一个值,而是但愿一个指针指向变量的值,由于在go语言中,函数的参数传递都是按拷贝传递。指针是一个内存地址。经过指针能够找到这个变量的实际的值,是一种间接的取值。
func main(){ goku := &Sanya{"sanya",9000} Super(goku) fm.Println(goku.Power) } func Super(s *Sanya){ s.Power = 10000 }
结果是10000,这样就是传递了指针。复制一个指针变量的开销比复制一个复制复杂的结构体小。
结构体没有构造函数,你能够建立一个函数返回一个相应类型的实例来代替:
func NewSanya(name string, province int) Sanya{ return Sanya{ Name:name, Province:province, } }
为新建立的对象分配内存:
goku := &Sanya{ name:"goku", province:23 }
对已定义对结构体进行扩展:
type Sanya struct{ Name string Province int Father *Sanya }
初始化:
gohan := &Sanya{ Name:"Sanya", Province:23, Father:&Sanya{ Name:"Haiko", Province:23, Father:nil, } }
当你写go代码时,很天然就会问本身,这里应该使用值类型仍是指针类型。若是你不肯定时,就使用指针。值传递是一种确保数据不可变对方法。有时候须要函数内对调用代码进行改变,须要使用指针。 即便你不打算改变数据,也要考虑大结构体拷贝的开销,若是小的结构体能够进行拷贝。
数组是固定大小的。声明数组时必须指定他们的大小,一旦数组大小被指定,他就不能扩展变大。
var scores [10]int scores[0] = 300 // 直接初始化一个有值的数组 scores := [4]int{9001,9002,9003,9004} // 遍历数组 for index,value:= range scores{ }
数组效率高可是不灵活,咱们处理数据时,通常不知道元素的数量,所以使用切片。
在go中你通常不多使用数组。会更多使用切片。切片是一个轻量级的结构体封装,这个结构体被封装后,表明一个数组的一部分。 建立切片时和建立数组不一样的是,不须要指定大小。
scores := []int{1,2,3,4} scores := make([]int,0,10) //长度为0可是容量为10的分片 scores := append(scores,5)
定义键值对,能够经过make建立:
lookup := make(map[stirng]int) lookup["goku"] = 9001
若是你已经装来git,执行以下命令:
go get github.com/mattn/go-sqlite3 go get将获得这些远程文件并将他们保存在你的工做空间。导入包到工做空间:
import( "github.com/mattn/go-sqlite3" )
接口是一种类型,他只定义了声明,没有具体实现。如:
type Logger interface{ Log(message string) }
接口能够在代码中实现解耦。
Go中Buffer高效拼接字符串及自定义线程安全Buffer:
Go中可使用“+”合并字符串,但这种方式效率很是低,每合并一次,都建立一个新的字符串,就必须遍历复制一次字符串。能够经过Buffer高效拼接字符串。 使用bytes.Buffer来组装字符串,不须要复制,只须要将添加字符串放在缓存末尾便可。因为Buffer中的write和read函数中都未发现锁的踪迹,因此Buffer的并不是是不安全的。
Go特有的并发编程模型方式:
Goroutine & Channel;
在Go世界里,每个并发执行的活动称为goroutine。 经过goroutine,能够实现并行运算,十分便捷。 go协程相似于一个线程,可是协程由go自身调度,不是系统。在协程中对代码能够和其余代码并发执行。
func main(){ fmt.Println("start") go process() time.Sleep(time.Millisecond * 10) fmt.Println("done") } func process(){ fmt.Println("processing") }
咱们如何启动一个协程对。只是简单对将go关键字附在要执行对函数前面便可。 go协程很容易建立且开销极小。最终多个go协程将会在同一个底层系统线程上运行。这也是常称为M:N线程模型,由于咱们有M个应用协程运行在N个系统线程上。结果就是,一个go协程对开销和系统线程比起来相对低(通常都是几十K)。在现代硬件上,能够跑成千上万对协程。 还隐藏了映射和调度的复杂性。并发执行让go本身去处理。主线程在退出前不会等待全部的协程执行完毕,因此主线程在退出前,协程才有机会执行,因此咱们必须让代码协同。
目标:可以处理从上百万个端点发来的大量POST请求。HTTP请求处理函数会收到包含不少payloads的JSON文档。这些payloads须要被写到Amazon S3上,接着有map-reduce系统来处理。
咱们一般会将请求放入队列,经过必定数量(例如经过核心CPU数)goroutine组成一个worker pool,worker pool中的worker读取队列执行任务,最理想的状况下,CPU的全部核都会并行的执行任务。
而后设置两个集群,一个用做处理HTTP请求,一个用做workers。这样能够根据处理后台的工做量进行扩容。
主Goroutine作了什么?
启动系统检测器;
设定通用配置,检查运行环境;
建立定时垃圾回收器;
执行main包的init函数;
执行main包的main函数;
进行一些善后处理工做;
建立一个协程没有难度,启动不少协程开销也不大。可是并发执行的代码须要协同。为了解决这个问题,go提供了管道(channels)。 协程会将代码函数拆分为不少汇编指令,在并发场景下,若是想安全的操做一个变量,惟一的手段就是读取该变量。能够任意多的读,但写必须同步。能够依赖于cpu架构的真正原子操做。更多时候使用一个互斥锁。
//定义锁 lock sync.Mutex //使用锁 lock.Lock() //开锁 defer lock.Unlock()
并发编程的挑战在于数据共享。若是你的go协程没有共享数据,就不须要担忧她们。可是现实场景中经常须要多个请求共享数据。通道用于go协程之间传递数据,go协程能够经过通道,传递数据到另外一个go协程。结果就是任什么时候候只有一个go协程能够访问数据。
Channel的Happens before原则:
发送操做开始->值拷贝(产生副本)->发送操做结束->接收操做开始->接收方持有值->接收操做结束。 Channel能够协调多个Goroutine的运行。
通道也有类型,就是将要在通道传递到数据的类型,如建立一个通道,这个通道能够用来传递一个整数:
c := make(chan int) // 将这个通道传递给一个函数 fun worker(c chan int){ } //通道发送数据 CHANNEL <- DATA //通道接收数据 VAR := <-CHANNEL
尖头指向的方向是数据的流动方向。 当咱们从一个通道接收或向通道发送数据时会阻塞,直到有数据。
定义一个数据处理者结构体:
type Worker struct{ id int } fun (w Worker) process(c chan int){ for{ data := <-c fat.Pringtf("worker data",w.id) } }
咱们的worker很简单,会一直等待数据,直到数据可用,而后处理它,他在一个循环中,永远尽职的等待更多的数据并处理。
启动多个worker:
c := make(chan int) for i:=0; I<4; I++{ worker := Worker{id:i} go worker.process(c) }
建立一些任务:
for{ c <- rand.Int() time.Sleep(time.Millisecond*50) }
咱们不知道哪一个worker将得到数据。但go能够确保往一个通道发送数据时,仅一个单独的接收器能够接收。通道提供了全部的同步代码。