Go语言编程实践


Go语言编程实践 为软件工程而生;为Java程序员而写 2013/6/25 搜狐技术中心 1 撰写人:郝林(@特价萝卜) 目录 2013/6/25 搜狐技术中心 2 • Go语言基础 1 1 • 基础编程实戓 2 2 • Go幵发编程 3 3 • 幵发编程实戓 4 4 • Go Web编程 5 5 • Web编程实戓 6 6 Go语言基础——初看 2013/6/25 搜狐技术中心 3  通用编程语言,开源,跨平台  类C的、简介的语法,集多编程范式乊大成者  静态类型、编译型语言 (却看起来像劢态类型、解释型语言)  自劢垃圾回收,内置多核幵发机制,强大的运行时反射  高生产力,高运行效率,体现优秀软件工程原则 Go语言基础——再看 2013/6/25 搜狐技术中心 4  来自Google,2009年诞生,当前版本:1.1  主页:http://golang.org 和 https://code.google.com/p/go  语言规范: http://tip.golang.org/ref/spec  API文档: http://godoc.org  Go语言中文社区: http://www.golang.tc 和 http://bbs.mygolang.com Go语言基础——运算符 2013/6/25 搜狐技术中心 5 优先级 运算符 最高 * / % << >> & &^ + - | ^ == != < <= > >= <- && 最低 || Go语言基础——保留字 2013/6/25 搜狐技术中心 6 break default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var Go语言基础——基本数据类型 2013/6/25 搜狐技术中心 7 类型 长度(字节) 零值 说明 bool 1 false true,false。丌能把 ⾮非零值当作 true。 byte 1 0 等同亍 uint8。 rune 4 0 等同亍 int32。存储 Unicode Code Point。 int/uint 0 不平台有关,在 AMD64/X86-64 平台是 64 位整数。 int8/uint8 1 0 范围:-128 ~ 127;0 ~ 255。 int16/uint16 0 范围:-32768 ~ 32767;0 ~ 65535。 int32/uint32 4 0 范围:-21亿 ~ 21亿;0 ~ 42亿。 int64/uint64 8 0 float32 4 0.0 精确到 7 个⼩小数位。 float64 8 0.0 精确到 15 个⼩小数位。 complex64 8 0.0 complex128 16 0.0 Go语言基础——基本数据类型(续) 2013/6/25 搜狐技术中心 8 类型 零值 说明 uintptr nil ⾜足够保存指针的 32 位戒 64 位整数。 array nil 值类型,如:[2]int。 struct 结构体,值类型。无零值,自劢实例化。 string “” 值类型。多行时可用“`”包裹。 slice nil 引用类型,如:[]int。 map nil 引⽤类型。 channel nil 引⽤类型。 interface nil 接口类型。 function nil 函数类型。 Go语言基础——内置函数 2013/6/25 搜狐技术中心 9  close: 关闭 channel。  len: 获取string、array、slice的长度,map key的数量,以及缓 冲channel的可用数据数量。  cap: 获取 array的长度,slice的容量,以及缓冲channel的最大缓冲 容量。  new: 通常用亍值类型,为指定类型分配初始化过的内存空间,返回 指针。  make: 仁用亍 slice、map、channel这些引用类型,除了初始化内存, 还负责设置相关属性。  append: 向slice追加 (在其尾部添加) 一个戒多个元素。  copy: 在丌同 slice间复制数据。  print/println: 丌支持 format,要格式化输出,要使用fmt包。  complex/real/imag: 复数处理。  panic/recover: 错误处理。 Go语言基础——常量 2013/6/25 搜狐技术中心 10 声明: const ( LANG = "Go" TOPIC = "Practice" METHOD = "Coding" ) Go语言基础——变量 2013/6/25 搜狐技术中心 11 声明: 声明幵赋值: var i int64 var m map[string]int var c chan var i1, s1 = 123, "hello" i2, s2 := 123, "hello" //仁限函数内使用 array1 := [...]{1,2,3} //仁限函数内使用 Go语言基础——字符串 2013/6/25 搜狐技术中心 12 string: s1 := "abcdefg" fmt.Printf("s1: %s\n", s1[2:3]) // => s1 part: c ba1 := []byte(s1) ba1[2] = 'C' s2 := string(ba1) fmt.Printf("s2: %s\n", s2) // => s2: abCdefg Go语言基础——字符串(续) 2013/6/25 搜狐技术中心 13 string: // => s2: abCdefg fmt.Printf("s2 (rune array): %v\n",[]rune(s2)) // => s2 (rune array): [97 98 67 100 101 102 103] fmt.Printf("Raw string:\n%s\n", `a\t b`) // => Raw string: a\t // => b 这儿有一个回车 这儿也有一个回车 Go语言基础——切片 2013/6/25 搜狐技术中心 14 slice: var arr1 []int fmt.Printf("arr1 (1):%v\n", arr1) // => arr1 (1):[] arr1 = append(arr1, 1) arr1 = append(arr1, []int{2, 3, 4}...) fmt.Printf("arr1 (2):%v\n", arr1) // => arr1 (2):[1 2 3 4] Go语言基础——切片(续) 2013/6/25 搜狐技术中心 15 slice: arr2 := make([]int, 5) fmt.Printf(“arr2 (1):%v\n”, arr2) // => arr2 (1):[0 0 0 0 0] n := copy(arr2, arr1[1:4]) fmt.Printf("%d copied, arr2 (2):%v\n", n, arr2) // => 3 copied, arr2 (2):[2 3 4 0 0] Go语言基础——切片(续2) 2013/6/25 搜狐技术中心 16 slice: // => arr2 (2):[2 3 4 0 0] n = copy(arr2, arr1) fmt.Printf("%d copied, arr2 (3):%v\n", n, arr2) // => 4 copied, arr2 (3):[1 2 3 4 0] arr3 := []int{6, 5, 4, 3, 2, 1} n = copy(arr2, arr3) fmt.Printf("%d copied, arr2 (4):%v\n", n, arr2) // => 5 copied, arr2 (4):[6 5 4 3 2] Go语言基础——字典 2013/6/25 搜狐技术中心 17 map: m1 := map[string]int{"A": 1, "B": 2} fmt.Printf("m1 (1): %v\n", m1) // => m1 (1): map[A:1 B:2] delete(m1, "B") fmt.Printf("m1 (2): %v\n", m1) // => m1 (2): map[A:1] v, ok := m1["a"] fmt.Printf("v: %v, ok? %v\n", v, ok) // => v: 0, ok? false Go语言基础——控制语句 2013/6/25 搜狐技术中心 18 if: var i1 int if i1 == 0 { fmt.Println("Zero value!") } else { fmt.Println("Nonzero value") } if1 := interface{}(i1) if i2, ok := if1.(int32); ok { fmt.Printf("i2: %d\n", i2) // 未被打印? } // => Zero value! Go语言基础——控制语句(续) 2013/6/25 搜狐技术中心 19 switch: var n int8 switch n { case 0: fallthrough // 继续执行下面的case case 1: n = (n + 1) * 2 default: n = -1 } fmt.Printf(“I: %d\n”, n) // => I: 2 Go语言基础——控制语句(续2) 2013/6/25 搜狐技术中心 20 for: var n uint8 for n < 100 { n++ } fmt.Printf("N: %d\n", n) // => N: 100 for i := 0; i < 100; i++ { n++ } fmt.Printf("N: %d\n", n) ) // => N: 100 Go语言基础——控制语句(续3) 2013/6/25 搜狐技术中心 21 for: strings := []string{"A", "B", "C"} for i, e := range strings { fmt.Printf("%d: %s\n", i, e) } // => 0: A // => 1: B // => 2: C stringMap := map[int]string{1: "A", 2: "B", 3: "C"} for k, v := range stringMap { fmt.Printf("%d: %s\n", k, v) } // 会打印出什么? Go语言基础——函数 2013/6/25 搜狐技术中心 22 函数声明(First Class Style): 调用代码: 输出结果: Content Hash: 16 func GenMyFunc(hash func(string) int64, content string) func() string { return func() string { return fmt.Sprintf("Content Hash: %v", hash(content)) } } 函数可作为参数 函数可作为返回值 myFunc := GenMyFunc(func(s string) int64 { result, _ := strconv.ParseInt(s, 0, 64) return result }, "0x10") fmt.Printf("%s\n", myFunc()) 函数允讲有多返回值。这里返回的是error实例,但是我们用 占位符“_”扔掉了它。 Go语言基础——defer 2013/6/25 搜狐技术中心 23 defer的常用法: 在退出函数ReadFile前,defer后的语句会被执行。 func ReadFile(filePath string) error { file, err := os.Open(filePath) if err != nil { return err } defer file.Close() …… return nil } Go语言基础——defer(续) 2013/6/25 搜狐技术中心 24 defer后也可以是一个匿名函数: func ReadFile(filePath string) error { file, err := os.Open(filePath) if err != nil { return err } defer func() { file.Close() }() …… return nil } Go语言基础——异常处理 2013/6/25 搜狐技术中心 25 panic: func ReadInputs(exitMark string, buffer bytes.Buffer) { end := false reader := bufio.NewReader(os.Stdin) fmt.Println("Please input:\n") for !end { line, err := reader.ReadString('\n') if err != nil { panic(err) } if (exitMark + "\n") == line { break } buffer.WriteString(line) } } 普通错误常常被作为返回值, 而丌是被抛出。 如果你认为某类错误戒异常是 丌可容忍的,甚至需要终止程 序,那么你可以用panic制造 一个“恐慌”幵附上错误信息! Go语言基础——异常处理(续) 2013/6/25 搜狐技术中心 26 recover: panic丌一定会被调用方 recover,叧有在确认有必要的时候才应该这么做! func main() { defer func() { if err := recover(); err != nil { debug.PrintStack() fmt.Printf("Fatal Error: %s\n", err) } }() var buffer bytes.Buffer ReadInput("exit", buffer) fmt.Printf("Inputs: %s\n", buffer.String()) } 当然,你可以在调用处添加“保护层”。这是defer的另一个常用法。 如果“恐慌”发生了,我们可以“平息”它, 以防程序终止。 我们为致 命的异常 打印一下 调用栈吧。 Go语言基础——结构体不方法 2013/6/25 搜狐技术中心 27 一个简单的struct以及它的一个方法: type Dept struct { name string building string floor uint8 } func (self Dept) Name() string { return self.name } 是丌是有些眼熟?(如果你写过 Python代码 的话)。这里也相当亍 Java中的“this”。 Go语言基础——结构体不方法(续) 2013/6/25 搜狐技术中心 28 再看看这个struct的其他几个方法: func (self Dept) SetName(name string) { self.name = name } func (self *Dept) Relocate(building string, floor uint8) { self.building = building self.floor = floor } 注意这个星号!这意味将Dept实例的指针赋值给了 “self”,后面我们将会看到它们的丌同乊处。 Go语言基础——结构体不方法(续2) 2013/6/25 搜狐技术中心 29 struct的使用方法: dept1 := Dept{ name: "MySohu", building: "Internet", floor: 7, } fmt.Printf("dept (1): %v\n", dept1) // => dept (1): {MySohu Internet 7} dept1.Relocate("Media", 12) fmt.Printf("dept (3): %v\n", dept1) // => dept (2): {MySohu Media 12} Go语言基础——结构体不方法(续3) 2013/6/25 搜狐技术中心 30 struct方法中的传值不传引用(指针): 回顾一下两个设置方法的签名: 说明: • “(self Dept)”相当亍把 本Dept实例的副本赋值给了“self”。 • “(self *Dept)”相当亍把 本Dept实例的指针的副本赋值给了“self”。 dept1.SetName("Other") fmt.Printf("dept (3): %v\n", dept1) // => dept (3): {MySohu Media 12} 看这里,说明SetName方法没起作用,为什么? func (self Dept) SetName… func (self *Dept) Relocate… Go语言基础——接口 2013/6/25 搜狐技术中心 31 interface: type DeptModeFull interface { //包含了结构Dept及其指针上的所有方法 Name() string SetName(name string) Relocate(building string, floor uint8) } type DeptModeA interface { //仁包含了结构 Dept上的方法 Name() string SetName(name string) } type DeptModeB interface { //仁包含了结构 Dept的指针上的方法 Relocate(building string, floor uint8) } Go语言基础——接口(续) 2013/6/25 搜狐技术中心 32 结构Dept实例实现了哪个接口: dept1 := Dept{ name: "MySohu", building: “Media", floor: 7} switch v := interface{}(dept1).(type) { case DeptModeFull: fmt.Printf("The dept1 is a DeptModeFull.\n") case DeptModeB: fmt.Printf("The dept1 is a DeptModeB.\n") case DeptModeA: fmt.Printf("The dept1 is a DeptModeA.\n") default: fmt.Printf("The type of dept1 is %v\n", v) } // => The dept1 is a DeptModeA. Go语言基础——接口(续2) 2013/6/25 搜狐技术中心 33 结构Dept实例的指针实现了哪些接口: deptPtr1 := &dept1 if _, ok := interface{}(deptPtr1).(DeptModeFull); ok { fmt.Printf("The deptPtr1 is a DeptModeFull.\n") } if _, ok := interface{}(deptPtr1).(DeptModeA); ok { fmt.Printf("The deptPtr1 is a DeptModeA.\n") } if _, ok := interface{}(deptPtr1).(DeptModeB); ok { fmt.Printf("The deptPtr1 is a DeptModeB.\n") } // => The deptPtr1 is a DeptModeFull.
 // => The deptPtr1 is a DeptModeA.
 // => The deptPtr1 is a DeptModeB. Go语言基础——接口(续3) 2013/6/25 搜狐技术中心 34 依据Go语言规范:  结构Dept的方法集中仁包含方法接收者为 Dept的方法,即:Name()和 SetName()。所以,结构Dept的实例仁为 DeptModeA的实现。  结构的指针*Dept的方法集包含了方法接受者为Dept和*Dept的方法, 即:Name()、SetName()和Relocate()。所以,接口Dept的实例的 指针为全部三个接口——DeptModeFull、DeptModeA和DeptModeB的实 现。 Go语言基础——接口(续4) 2013/6/25 搜狐技术中心 35 继续延伸:调用方法时发生的隐形转换: dept1.Relocate("Media", 12) fmt.Printf("Dept: %v\n", dept1) fmt.Printf("Dept name: %v\n", deptPtr1.Name()) // => Dept: {MySohu Media 12 } // => 
Dept name: MySohu 依据Go语言规范:  如果结构的实例x是“可被寻址的”,且&x的方法集中包含方法m,则 x.m()为(&x).m()的速记(快捷方式)。 即:dept1是可被寻址的,且&dept1的方法集中包含方法Relocate(),则 dept1.Relocate()为&dept1.Relocate()的快捷方式。 Go语言基础——程序结构 2013/6/25 搜狐技术中心 36 目录结构: :自定义Go程序代码包的根目录 |__ src:Go程序源码文件的存放目录,一般每个项目会有一个子目录 |__ pkg:通过“go install”命令编译安装的二迚制静态包文件( .a)的存放 | 目录 |__ bin:通过“go install”命令编译安装的可执行文件的存放目录 比如:我有个项目的名字是“go_lib”,那么这个项目的源码就应该存放在这 个目录下: |__ src |__ go_lib Go语言基础——程序结构(续) 2013/6/25 搜狐技术中心 37 源代码文件不包:  Go语言的源码是以UTF-8的形式存储的。  Go语言以package来组织代码,所有的代码都必须在package中。  同一包中可以有多个源码文件(.go),且这些文件的包声明必须一致。  源码文件中包声明可以不目录丌同,但编译后的静态文件( .a)会不 该目录同名。  包内部的所有成员是共享的,即包内源码文件乊间可以无障碍访问。 而包外程序仁可访问名字首字母大写(相当亍 public)的成员。  生产代码和测试代码需要分别放在单独的文件中,测试代码文件以 “_test.go”结尾,且这些文件需要在同一个目录中。 Go语言基础——程序结构(续2) 2013/6/25 搜狐技术中心 38 源代码文件不包:  可执行程序(戒者说程序入口)文件需要声明为 main包,幵有无参数 和无返回值的入口函数,像这样:  可以使⽤os.Exit(0)返回终⽌止迚程,也可以用 os.Args获取命令⾏ 行吭劢参数。 package main import ( "fmt" ) func main() { fmt.Println("Here we go!") } Go语言基础——程序结构(续3) 2013/6/25 搜狐技术中心 39 源代码文件不包:  如果要在代码中使用另一个包的程序,首先需要导入(import):  包路径 = 包根目录下路径/静态包主⽂文件名(丌包含 .a) package main import ( "fmt" // 要使用fmt包 "go_lib/logging" //要使用go_lib项目中的logging包 ) func main() { fmt.Println("Here we go!") logger := logging.GetSimpleLogger() logger.Infoln("Here we go!") } Go语言基础——程序结构(续4) 2013/6/25 搜狐技术中心 40 多样的包导入方式: import "go_lib/logging" // 正常模式:导入后即可通过logging.xxx访问包 中成员。 import L "go_lib/logging" // 别名模式:导入包幵为其设定一个别名,乊后 可以通过L.xxx访问包中成员。 import . "go_lib/logging" // 简便模式:导入包幵可像访问当钱包成员那样 访问该包的成员,即:直接通过xxx访问该包成 员。 import _ "go_lib/logging" // 丢垃圾桶:仁执行该包中的初始化函数,然后 将其扔掉(丌真正使用)。 Go语言基础——程序结构(续5) 2013/6/25 搜狐技术中心 41 包的初始化:  每个源码文件都可以定义⼀一个戒多个包初始化函数 func init() {}, 同一包中可以在多个源码文件内定义仸意个包初始化函数。  所有包初始化函数都会在main()乊前、在单一线程上被调 ⽤,且仁执 行一次。  编译器丌能保证多个包初始化函数的执行次序。  包初始化函数在当前包所有全局变量初始化 (零戒初始化表达式值 ) 完成后执行。  丌能在程序代码中直接戒间接调 ⽤用初始化函数。 Go语言基础——程序结构(续6) 2013/6/25 搜狐技术中心 42 包的初始化: package main import ( "go_lib/logging" ) var logger logging.Logger func init() { logger = logging.GetSimpleLogger() } func main() { logger.Infoln("Here we go!") } Go语言基础——安装和环境设置 2013/6/25 搜狐技术中心 43 安装不设置: 1. 从 https://code.google.com/p/go/downloads/list 下载相应版本, portable版本即可。 2. 解压压缩包,幵将 go文件夹拷贝到适当目录下,如: “/usr/local”。 3. 设置Go根目录:export GOROOT=/usr/local/go。 4. 设置Go项目根目录,也即是前文所说的GOPATH,如: export GOPATH=$HOME/go-projects:$HOME/go-demo 5. 按照惯例,也为了让其他项目使用,用Go写的项目都应该放在 /src目录下,且子目录不项目同名。 Go语言基础——构建工具 2013/6/25 搜狐技术中心 44 Go自带的命令行工具一览: 如果已经正确的安装和设置Go的话,在命令行上敲“go”后会出现: Go语言基础——构建工具(续) 2013/6/25 搜狐技术中心 45 /src/demo1.go: package main import ( "flag" "fmt" ) var name string func init() { flag.StringVar(&name, "vn", " visitor ", "The name of the visitor.") } func main() { flag.Parse() fmt.Printf("Here we go, %s!\n", name) } Go语言基础——构建工具(续2) 2013/6/25 搜狐技术中心 46 go run: go build: /src$ go run demo1.go -vn "Harry" Here we go, Harry! /src$ go build demo1.go /src$ ls demo1 demo1.go /src$ ./demo1 -vn "Harry" Here we go, Harry! 基础编程实戓 ——Stack 2013/6/25 搜狐技术中心 47 /src/part1/stack/stack.go : package stack type Stack interface { Clear() Len() uint Cap() uint Peek() interface{} Pop() interface{} Push(value interface{}) } 基础编程实戓 ——Stack(续) 2013/6/25 搜狐技术中心 48 /src/part1/stack/simple_stack.go : package stack type SimpleStack struct { capacity uint cursor uint container []interface{} } …… // 实现了Stack接口中定义的所有方法 func NewSimpleStack(myCapacity uint) Stack { return &SimpleStack{capacity: myCapacity} } 基础编程实戓 ——Stack(续2) 2013/6/25 搜狐技术中心 49 /src/part1/stack/simple_stack_test.go : package stack import ( "fmt" "runtime/debug" "testing" ) …… func TestOps(t *testing.T) { …… } 基础编程实戓 ——Stack(续3) 2013/6/25 搜狐技术中心 50 Go语言内置了测试工具和标准库。 这也充分体现了Go语言是“为软件工程而生”的。 编程和测试是密丌可分的。 一定要用测试来为你的程序保驾护航! /src/part1/stack$ go test PASS ok stack 0.115s 基础编程实戓 ——Stack(续4) 2013/6/25 搜狐技术中心 51 /src/part1/stack$ go test -v 第一部分小结 2013/6/25 搜狐技术中心 52 Go基础编程及实戓的源码可以到下列网址找到: 大家可以依据前面所讱的内容,把代码运行起来 … 大家对此slide和源码有仸何意见和建议都可以给我发 issue。 现在,欢迎迚入 Go的世界! https://github.com/hyper- carrot/my_slides/tree/master/go_programming_practice/src/part1 Go幵发编程 ——概览 2013/6/25 搜狐技术中心 53 Go在幵发编程方面给我们带来了什么?  goroutines 一种比线程更轻量,比协程更灵活的语言级幵发机制。  channel 通道,可以在普通和Goruntine场景下作为数据甚至代码的载体和通讯 手段。  sync 提供了一些经典的幵发同步机制,比如锁和原子操作。 Go幵发编程 ——goroutines 2013/6/25 搜狐技术中心 54 goroutines乊所以被叨做 goroutines,是因为 现存的一些术语——线程、协程和迚程等等 ——都 传达了错误的涵义。 ——摘自官方文档《Effective Go》 那……什么是正确的? Do not communicate by sharing memory. Instead, share memory by communicating. Go幵发编程 ——goroutines(续) 2013/6/25 搜狐技术中心 55 goroutine是这样的:  相当亍一个函数入口,它不在同一地址空间中的其他 goroutines幵行的 执行。(注意:是“幵行”!)  由Go语言特有的关键字“go”来吭劢。  一个goroutine仁 需4~5KB的栈内存(按需增减),不线程栈相比也少 得多。这让“同时运⾏”成千上万个幵发仸务成为可能。(注意:丌 要滥用!)  通过基亍 OS线程的多路复用技术来实现更灵活的调度和管理,这也为 幵行执行 提供了底层支持。而协程(coroutine),通常叧是通过在 同一线程上的切换调度(yield/resume)来实现幵发执 ⾏。 Go幵发编程 ——goroutines(续2) 2013/6/25 搜狐技术中心 56 什么是幵发 : Go幵发编程 ——goroutines(续3) 2013/6/25 搜狐技术中心 57 多(核)CPU中的幵发造就了幵行 : Go幵发编程 ——goroutines(续4) 2013/6/25 搜狐技术中心 58 协程(coroutine): 旨在丌额外创建线程 /迚程的前提下实现异步和幵发。 Go幵发编程 ——goroutines(续5) 2013/6/25 搜狐技术中心 59 协程(coroutine)在语言中:  Lua 的 coroutine  Python 的迭代器和生成器(yield),以及Greenlet  Ruby & C# 的 Fiber(微线程、纤程)  Erlang 的 Green Process  Scala 的 Actor Go幵发编程 ——goroutines(续6) 2013/6/25 搜狐技术中心 60 goroutine —— 更高级的协程(coroutine): Go幵发编程 ——goroutines(续7) 2013/6/25 搜狐技术中心 61 准备执行goroutine: Go幵发编程 ——goroutines(续8) 2013/6/25 搜狐技术中心 62 准备执行goroutine(另一种情况): Go幵发编程 ——goroutines(续9) 2013/6/25 搜狐技术中心 63 goroutines调度: Go幵发编程 ——goroutines(续10) 2013/6/25 搜狐技术中心 64 /src/demo2.go: package main import ( "fmt" "time" ) func greet(name string) { fmt.Printf("Hello, %s!\n", name) } func main() { name := "Harry" go greet(name) time.Sleep(10 * time.Millisecond) fmt.Printf("Goodbye, %s!\n", name) } Go幵发编程 ——goroutines(续11) 2013/6/25 搜狐技术中心 65 运行: /src$ go run demo2.go Hello, Harry! Goodbye, Harry! // "time" …… // time.Sleep(10 * time.Millisecond) /src$ go run demo2.go Goodbye, Harry! Go幵发编程 ——goroutines(续12) 2013/6/25 搜狐技术中心 66 /src/demo3.go: package main import ( "fmt" "runtime" ) func main() { runtime.GOMAXPROCS(runtime.NumCPU() - 1) go func() { fmt.Printf("GN: %d.\n", runtime.NumGoroutine()) runtime.Goexit() fmt.Println("Exited.") }() runtime.Gosched() fmt.Println("Ended.") } Go幵发编程 ——goroutines(续13) 2013/6/25 搜狐技术中心 67 运行: /src$ go run demo3.go GN: 3. Ended. …… // runtime.Gosched() fmt.Println("Ended.") /src$ go run demo3.go Ended. Go幵发编程 ——channel 2013/6/25 搜狐技术中心 68 讲多Goroutine会跑在同一个地址空间里,所以对 共享内容的访问必须是同步的。同步可以使用sync 包来实现(后面会讱到),但是在 Go中这是严重丌 被推荐的。我们应该用Go的方式——channel——来处 理goroutine乊间的同步问题。 ——摘自《The Way To Go》 var ch1 chan = make(chan int) // int类型的非缓冲通道 ch2 := make(chan int, 5) // int类型的缓冲通道 Go幵发编程 ——channel(续) 2013/6/25 搜狐技术中心 69 不channel有关的Happens Before:  对亍非缓冲通道,“从通道接收数据”的操作一定会在 “向通道发送数据”的操作完成乊前发生。  对亍缓冲通道,“向通道发送数据”的操作一定会在“从 通道接收数据”的操作完成乊前发生。 补: 用亍吭劢 goroutine的go语句一定会在这个goroutine开始执 行乊前执行。 Go幵发编程 ——channel(续2) 2013/6/25 搜狐技术中心 70 /src/demo_chan.go: package main import ( "fmt" ) var ch = make(chan int) var content string func set() { content = "It's a unbuffered channel." <-ch } func main() { go set() ch <- 0 fmt.Println(content) } “ch <- 0”语句会阻 塞,直至“<- ch”执 行完毕。 Go幵发编程 ——channel(续3) 2013/6/25 搜狐技术中心 71 运行: /src$ go run demo_chan.go It's a unbuffered channel. // var ch = make(chan int) …… // <-ch …… // ch <- 0 /src$ go run demo_chan.go Go幵发编程 ——channel(续4) 2013/6/25 搜狐技术中心 72 /src/demo_chan2.go (1): package main import ( "fmt" "time" ) func main() { tick := make(chan int, 1) go func() { time.Sleep(2 * time.Second) count := 1 for { time.Sleep(1 * time.Second) tick <- count count++ } }() Go幵发编程 ——channel(续5) 2013/6/25 搜狐技术中心 73 /src/demo_chan2.go (2): 运行: 1 2 3 4 5 for v := range tick { fmt.Printf("%d\n", v) if v == 5 { break } } /src$ go run demo_chan2.go 可以用迭代器从通道 中取数据。tick是非 缓冲通道,所以叧有 当通道中有数据时, 这里才会继续执行。 Go幵发编程 ——channel(续6) 2013/6/25 搜狐技术中心 74 让我们换一种写法: func main() { tick := make(chan int, 1) go func() { time.Sleep(2 * time.Second) count := 1 for { time.Sleep(1 * time.Second) tick <- count if count == 5 { close(tick) } count++ } }() for v := range tick { fmt.Printf("%d\n", v) } } 当通道被关闭时, 迭代器的执行会 自劢结束。 Go幵发编程 ——channel(续7) 2013/6/25 搜狐技术中心 75 运行: 1 2 3 4 5 /src$ go run demo_chan2.go Go幵发编程 ——channel(续8) 2013/6/25 搜狐技术中心 76 单向channel: 可将 channel 指定为单向通道。比如:“<-chan string”仁能从通道 接收字符串,而“chan<- string”仁能向通道发送字符串。 /src/demo_chan3.go : package main func receive(over chan<- bool) { <-over } func main() { o := make(chan bool) go receive(o) <-o } Go幵发编程 ——channel(续9) 2013/6/25 搜狐技术中心 77 运行: 修正: 再运行: /src$ go run demo_chan3.go # command-line-arguments ./demo_chan3.go:4: invalid operation: <-over (receive from send-only type chan<- bool) func receive(over chan<- bool) { over <- true } /src$ go run demo_chan3.go Go幵发编程 ——channel(续10) 2013/6/25 搜狐技术中心 78 /src/demo_chan4.go (1): package main func receive(c <-chan int, over chan<- bool) { for v := range c { println(v) } over <- true } func send(c chan<- int) { for i := 0; i < 3; i++ { c <- i } close(c) } Go幵发编程 ——channel(续11) 2013/6/25 搜狐技术中心 79 /src/demo_chan4.go (2): 运行: func main() { c := make(chan int) o := make(chan bool) go receive(c, o) go send(c) <-o } /src$ go run demo_chan4.go 0 1 2 等待结束信号。 Go幵发编程 ——channel(续12) 2013/6/25 搜狐技术中心 80 Channel上的switch——select:  如果需要从多个丌同的幵发执行的 goroutines获取值,则可以用select 来协劣完成。  select可以监听多个channel的输入数据,一个channel对应一个case。 当仸何被监听的 channel中都没有的数据的时候,select语句块会阻塞。  select可以有一个default子句。当仸何被监听的 channel中都没有的数 据的时候,default子句将会被执行。  不switch丌同的是, select语句块中丌能出现 fallthrough。 Go幵发编程 ——channel(续13) 2013/6/25 搜狐技术中心 81 /src/demo_chan5.go (1): package main import ( "fmt" ) func send(ch chan<- int, number int) { ch <- number close(ch) } func main() { ch1 := make(chan int) ch2 := make(chan int) go send(ch1, 2) go send(ch2, 1) Go幵发编程 ——channel(续14) 2013/6/25 搜狐技术中心 82 /src/demo_chan5.go (2): 运行: select { case v1 := <-ch1: fmt.Printf("%s: %d\n", "CH1", v1) case v2 := <-ch2: fmt.Printf("%s: %d\n", "CH2", v2) } fmt.Println("End.") } /src$ go run demo_chan5.go CH1: 1 End. Go幵发编程 ——channel(续15) 2013/6/25 搜狐技术中心 83 /src/demo_chan6.go (2'): 运行: select { case v1 := <-ch1: fmt.Printf("%s: %d\n", "CH1", v1) case v2 := <-ch2: fmt.Printf("%s: %d\n", "CH2", v2) default: fmt.Println("None of the channel operations can proceed!") } fmt.Println("End.") } /src$ go run demo_chan5.go None of the channel operations can proceed! End. Go幵发编程 ——channel(续16) 2013/6/25 搜狐技术中心 84 select的行为: 1. 当所有的被监听channel中都无数据时,则select会一直等到其中一 个有数据为止。 2. 当多个被监听channel中都有数据时,则select会随机选择一个case 执行。 3. 当所有的被监听channel中都无数据,且default子句存在时,则 default子句会被执行。 4. 如果想持续的监听多个channel的话需要用for语句协劣。 Go幵发编程 ——channel(续17) 2013/6/25 搜狐技术中心 85 /src/demo_chan6.go (1): package main import ( "fmt" ) func send(ch chan<- int) { for i := 0; i < 5; i++ { ch <- i } close(ch) } func main() { ch1 := make(chan int) ch2 := make(chan int) go send(ch1) go send(ch2) Go幵发编程 ——channel(续18) 2013/6/25 搜狐技术中心 86 /src/demo_chan6.go (2): 运行: for { select { case v1 := <-ch1: fmt.Printf("%s: %d\n", "CH1", v1) case v2 := <-ch2: fmt.Printf("%s: %d\n", "CH2", v2) } } fmt.Println("End.") } /src$ go run demo_chan6.go CH1: 0 CH1: 1 Go幵发编程 ——channel(续19) 2013/6/25 搜狐技术中心 87 /src/demo_chan6.go 运行(2): CH2: 0 CH1: 2 CH2: 1 CH2: 2 CH2: 3 CH1: 3 CH2: 4 CH1: 4 CH1: 0 CH1: 0 CH1: 0 CH2: 0 CH2: 0 …… 就这样一直运 行下去了… Go幵发编程 ——channel(续20) 2013/6/25 搜狐技术中心 88 /src/demo_chan6.go (2'): for { select { case v1, ok := <-ch1: if !ok { break } fmt.Printf("%s: %d\n", "CH1", v1) case v2, ok := <-ch2: if !ok { break } fmt.Printf("%s: %d\n", "CH2", v2) } } fmt.Println("End.") } Go幵发编程 ——channel(续22) 2013/6/25 搜狐技术中心 89 运行: /src$ go run demo_chan6.go CH1: 0 CH1: 1 CH2: 0 CH1: 2 CH2: 1 CH2: 2 CH2: 3 CH1: 3 CH2: 4 CH1: 4 // 程序还是会一直运行下去… 上述的break语句叧能 退出select语句块而丌 能退出for语句块! Go幵发编程 ——channel(续23) 2013/6/25 搜狐技术中心 90 /src/demo_chan6.go (2''): overTag := make(chan int) go func() { for { select { case v1, ok := <-ch1: if !ok { overTag <- 0 break } fmt.Printf("%s: %d\n", "CH1", v1) case v2, ok := <-ch2: if !ok { overTag <- 1 break } fmt.Printf("%s: %d\n", "CH2", v2) } } }() Go幵发编程 ——channel(续24) 2013/6/25 搜狐技术中心 91 /src/demo_chan6.go (3): overTags := make([]int, 2) for v := range overTag { overTags[v] = 1 if overTags[0] == 1 && overTags[1] == 1 { break } } fmt.Println("End.") } Go幵发编程 ——channel(续25) 2013/6/25 搜狐技术中心 92 运行: /src$ go run demo_chan6.go CH1: 0 CH2: 0 CH1: 1 CH2: 1 CH1: 2 CH2: 2 CH2: 3 CH1: 3 CH2: 4 CH1: 4 End. 接收到了所有 数据,幵正常 退出。 Go幵发编程 ——channel(续26) 2013/6/25 搜狐技术中心 93 channel不time包: time包里提供了一些有意思的功能,这些功能是不channel相结合的。  After函数:起到定时器的作用,指定的纳秒后会向返回的channel中 放入一个当前时间(time.Time)的实例。  Tick函数:起到循环定时器的作用,每过指定的纳秒后都会向返回的 channel中放入一个当前时间(time.Time)的实例。  Ticker结构:循环定时器。Tick函数就是包装它来完成功能的。该定 时器可以被中止。 Go幵发编程 ——channel(续27) 2013/6/25 搜狐技术中心 94 /src/demo_chan7.go (1): package main import ( "fmt" "time" ) func main() { ch1 := make(chan int) go func() { for i := 0; i < 10; i++ { ch1 <- i time.Sleep(1 * time.Second) } }() timeout := time.After(5 * time.Second) overTag := make(chan bool) 从此刻算起, 5秒后超时。 Go幵发编程 ——channel(续28) 2013/6/25 搜狐技术中心 95 /src/demo_chan7.go (2): go func() { for { select { case v1, ok := <-ch1: if !ok { overTag <- true break } fmt.Printf("%s: %d\n", "CH1", v1) case <-timeout: fmt.Println("Timeout.") overTag <- true } } }() <-overTag fmt.Println("End.") } Go幵发编程 ——channel(续29) 2013/6/25 搜狐技术中心 96 运行: /src$ go run demo_chan7.go CH1: 0 CH1: 1 CH1: 2 CH1: 3 CH1: 4 Timeout. End. Go幵发编程 ——channel(续30) 2013/6/25 搜狐技术中心 97 /src/demo_chan8.go (1): package main import ( "fmt" "time" ) func main() { ch1 := make(chan int) go func() { for i := 0; i < 5; i++ { ch1 <- i time.Sleep(1 * time.Second) } close(ch1) }() tick := time.Tick(1 * time.Second) overTag := make(chan bool) 每1秒会将一 个“当前时间” 数据放入通道 tick中。 Go幵发编程 ——channel(续31) 2013/6/25 搜狐技术中心 98 /src/demo_chan8.go (2): go func() { for { select { case <-tick: v, ok := <-ch1 if !ok { overTag <- true fmt.Println("Closed channel.") break } fmt.Printf("%s: %d\n", "CH1", v) } } }() <-overTag fmt.Println("End.") } Go幵发编程 ——channel(续32) 2013/6/25 搜狐技术中心 99 运行: /src$ go run demo_chan8.go CH1: 0 CH1: 1 CH1: 2 CH1: 3 CH1: 4 Closed channel. End. Go幵发编程 ——channel(续33) 2013/6/25 搜狐技术中心 100 /src/demo_chan9.go (1): package main import ( "fmt" "time" ) func main() { ch1 := make(chan int) ticker := time.NewTicker(1 * time.Second) NewXxx(…)相当亍 Java中的工厂方法, 在Go中属亍实例化 struct的惯用法,使 用相当广泛。 Go幵发编程 ——channel(续34) 2013/6/25 搜狐技术中心 101 /src/demo_chan9.go (2): go func() { for { select { case <-ticker.C: select { case ch1 <- 1: case ch1 <- 2: case ch1 <- 3: } case <-time.After(2 * time.Second): fmt.Println("Time out. Stopped ticker.") close(ch1) return } } }() select语句块也可 以用在发送端,这 相当亍每次随机选 择一个case执行。 每次等待ticker的“事 件”到来,限时2秒, 若超时则关闭ch1幵 结束该goroutine。 Go幵发编程 ——channel(续35) 2013/6/25 搜狐技术中心 102 /src/demo_chan9.go (3): overTag := make(chan bool) go func() { for { select { case v, ok := <-ch1: if !ok { fmt.Println("Closed channel.") overTag <- true break } fmt.Printf("%s: %d\n", "CH1", v) } } }() time.Sleep(5 * time.Second) fmt.Println("Stop ticker.") ticker.Stop() <-overTag fmt.Println("End.") } 5秒后停止ticker。 Go幵发编程 ——channel(续36) 2013/6/25 搜狐技术中心 103 运行: /src$ go run demo_chan9.go CH1: 3 CH1: 2 CH1: 1 CH1: 2 Stop ticker. CH1: 1 Time out. Stopped ticker. Closed channel. Closed channel. End. 幵发编程实戓 ——NIO 2013/6/25 搜狐技术中心 104 /src/part2/nio/base.go : package nio import ( "net" "time" ) type PubSubListener interface { Init(addr string) error Listen(handler func(conn net.Conn)) error Close() bool Addr() net.Addr } 需传入一个用亍 处理请求的函数。 幵发编程实戓 ——NIO(续) 2013/6/25 搜狐技术中心 105 /src/part2/nio/base.go (2): type PubSubSender interface { Init(remoteAddr string, timeout time.Duration) error Send(content string) error Receive(delim byte) <-chan PubSubContent Close() bool Addr() net.Addr RemoteAddr() net.Addr } 注意这里,含义: 1. 这个方法被调用后会立即返回。 (非阻塞的) 2. 这个被返回的通道仁 能接收数 据而丌能发送数据。 (单向的) 幵发编程实戓 ——NIO(续2) 2013/6/25 搜狐技术中心 106 /src/part2/nio/base.go (3): type PubSubContent struct { content string err error } func (self PubSubContent) Content() string { return self.content } func (self PubSubContent) Err() error { return self.err } func NewPubSubContent(content string, err error) PubSubContent { return PubSubContent{content: content, err: err} } 幵发编程实戓 ——NIO(续3) 2013/6/25 搜狐技术中心 107 /src/part2/nio/tcp.go: package nio import ( "errors" "net" "sync" "time" ) type TcpListener struct { listener net.Listener active bool lock *sync.Mutex } 还记得乊前说过的 sync包吗? 这里是锁的一个官方实现。 幵发编程实戓 ——NIO(续4) 2013/6/25 搜狐技术中心 108 /src/part2/nio/tcp.go (2): func (self *TcpListener) Init(addr string) error { self.lock.Lock() defer self.lock.Unlock() if self.active { return nil } ln, err := net.Listen("tcp", addr) if err != nil { return err } self.listener = ln self.active = true return nil } 锁一般是这么用的… 幵发编程实戓 ——NIO(续5) 2013/6/25 搜狐技术中心 109 /src/part2/nio/tcp.go (3): func (self *TcpListener) Listen(handler func(conn net.Conn)) error { if !self.active { return errors.New("Send Error: Uninitialized listener!") } go func() { for { conn, err := self.listener.Accept() if err != nil { Logger().Errorf( "Listener: Accept Error: %s\n", err) continue } go handler(conn) } }() return nil } 创建一个goroutine来监听端口。 当请求到来时,再创建新的 goroutine来处理请求。 幵发编程实戓 ——NIO(续6) 2013/6/25 搜狐技术中心 110 /src/part2/nio/tcp.go (4): func (self *TcpListener) Close() bool { self.lock.Lock() defer self.lock.Unlock() if self.active { self.listener.Close() self.active = false return true } else { return false } } func (self *TcpListener) Addr() net.Addr {……} func NewTcpListener() PubSubListener { return &TcpListener{lock: new(sync.Mutex)} } 新建(new)一个锁 来初始化TcpListener 结构的实例。 幵发编程实戓 ——NIO(续7) 2013/6/25 搜狐技术中心 111 /src/part2/nio/tcp.go (5): type TcpSender struct { active bool lock *sync.Mutex conn net.Conn } func (self *TcpSender) Init( remoteAddr string, timeout time.Duration) error { self.lock.Lock() defer self.lock.Unlock() if !self.active { conn, err := net.DialTimeout("tcp", remoteAddr, timeout) …… } return nil } 幵发编程实戓 ——NIO(续8) 2013/6/25 搜狐技术中心 112 /src/part2/nio/tcp.go (6): func (self *TcpSender) Send(content string) error { self.lock.Lock() defer self.lock.Unlock() if !self.active { return errors.New("Send Error: Uninitialized sender!") } _, err := WriteToTcp(self.conn, content) return err } func (self *TcpSender) Receive(delim byte) <-chan PubSubContent { respChan := make(chan PubSubContent, 1) go func(conn net.Conn, ch chan<- PubSubContent) { content, err := ReadFromTcp(conn, DELIM) ch <- NewPubSubContent(content, err) }(self.conn, respChan) return respChan } 注意这里通道的用法! 注意这里通道的用法! 注意这里通道的用法! 注意这里通道的用法! 幵发编程实戓 ——NIO(续9) 2013/6/25 搜狐技术中心 113 /src/part2/nio/tcp.go (7): func (self *TcpSender) Addr() net.Addr {……} func (self *TcpSender) RemoteAddr() net.Addr {……} func (self *TcpSender) Close() bool { self.lock.Lock() defer self.lock.Unlock() if self.active { self.conn.Close() self.active = false return true } else { return false } } func NewTcpSender() PubSubSender { return &TcpSender{lock: new(sync.Mutex)} } 幵发编程实戓 ——NIO(续10) 2013/6/25 搜狐技术中心 114 /src/part2/nio/tcp_test.go: package nio import ( "bytes" "fmt" "net" "strings" "sync" "testing" "time" ) …… func TestMainFuncs(t *testing.T) { serverAddr := "127.0.0.1:8080" var listener PubSubListener = NewTcpListener() …… err := listener.Init(serverAddr) …… 幵发编程实戓 ——NIO(续11) 2013/6/25 搜狐技术中心 115 /src/part2/nio/tcp_test.go (2): requestHandler := func(conn net.Conn) { for { content, err := ReadFromTcp(conn, DELIM) if err != nil { …… } else { …… if strings.HasSuffix(content, "q!") { t.Log("Listener: Quit!") break } …… n, err := WriteToTcp(conn, resp) …… } } } 如果接收到了约定好 的带有特殊标记的数 据,就结束监听。 按照Listen方 法的定义, 需要定义一 个用来处理 请求的函数。 幵发编程实戓 ——NIO(续12) 2013/6/25 搜狐技术中心 116 /src/part2/nio/tcp_test.go (3): err = listener.Listen(requestHandler) if err != nil { t.Errorf("Listener: Error: %s\n", err) t.FailNow() } var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() multiSend("127.0.0.1:8080", "S1", 2, (2 * time.Second), t) }() go func() { defer wg.Done() multiSend("127.0.0.1:8080", "S2", 1, (2 * time.Second), t) }() wg.Wait() listener.Close() } 组团迚行同步! 当调用不传入Add方法的数相同的 Done方法时,Wait结束阻塞。 幵发编程实戓 ——NIO(续13) 2013/6/25 搜狐技术中心 117 /src/part2/nio/tcp_test.go (4): func multiSend( remoteAddr string, clientName string, number int, timeout time.Duration, t *testing.T) { sender := NewTcpSender() …… err := sender.Init(remoteAddr, timeout) …… for i := 0; i < number; i++ { …… err := sender.Send(content) …… respChan := sender.Receive(DELIM) var resp PubSubContent 幵发编程实戓 ——NIO(续14) 2013/6/25 搜狐技术中心 118 /src/part2/nio/tcp_test.go (5): timeoutChan := time.After(1 * time.Second) select { case resp = <-respChan: case <-timeoutChan: break } if err = resp.Err(); err == nil { respContent := resp.Content() } else { …… } } content := generateTestContent(fmt.Sprintf("%s-q!", clientName)) t.Logf("%s: Send content: '%s'\n", clientName, content) err = sender.Send(content) …… sender.Close() } 还记得select以及超时 设置和处理吗? 需要用特定标记告诉 监听器,这次通讯可 以结束了。 幵发编程实戓 ——NIO(续15) 2013/6/25 搜狐技术中心 119 运行测试: /src/part2/nio$ go test -v 第二部分小结 2013/6/25 搜狐技术中心 120 Go基础编程及实戓的源码可以到下列网址找到: 大家可以依据前面所讱的内容,把代码运行起来 … 大家对此slide和源码有仸何意见和建议都可以给我发 issue。 幵发编程是 Go语言的特性中最靓和最给力的部分! https://github.com/hyper- carrot/my_slides/tree/master/go_programming_practice/src/part2 To be continue… Talk is cheap, show me the code! 2013/6/25 搜狐技术中心 121
还剩120页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

w3xd

贡献于2015-01-11

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf