Go语言入门笔记

Go语言入门笔记

Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言,它用批判吸收的眼光,融合C语言、Java等众家之长,将简洁、高效演绎得淋漓尽致。

Go语言起源于2007年,当时Google的技术大神们备受C++越来越臃肿的困扰,决心开发一种新的语言来取代C++。他们认为:与其在臃肿的语言上不断增加新的特性,不如简化编程语言。于是,Golang这门新语言应运而生。

在十年多的时间里,Go语言发展势头强劲,凭借其简洁、高效的特性,在竞争激烈的编程语言市场中占据了一席之地。Google、腾讯、阿里等大公司纷纷选择使用Go语言来开发服务应用项目。当然,和其他的编程语言一样,Go语言也有其自身的缺陷。

课程导论

  • 特点
    • 没有“对象”,没有继承,没有泛型,没有 try/catch
    • 有接口,函数式编程,CSP 并发模型(goroutine + channel)
    • 语法简单
  • 基本语法
    • 变量
    • 选择,循环
    • 指针,数组,容器
  • 面向接口
    • 结构体
    • duck typing 的概念
    • 组合的思想
  • 函数式编程
    • 闭包的概念
  • 工程化
    • 资源管理,错误处理
    • 测试和文档
    • 性能调优
  • 并发编程
    • goroutine 和 channel
    • 理解调度器

基本语法

HelloWorld

package main

import "fmt"

func main() {
fmt.Println("Hello World!")
}

变量定义

package main

import "fmt"

// 默认变量值
func variableZeroValue() {
var a int
var s string
fmt.Println(a, s)
}

// 定义变量值
func variableInitialValue() {
var a, b int = 3, 4
var s string = "abc"
fmt.Println(a, b, s)
}

// 变量推断
func variableTypeDeduction() {
var a, b, c = 1, "abc", true
fmt.Println(a, b, c)
}

// 变量推断简写
func variableShorter() {
a, b, c := 1, "abc", true
fmt.Println(a, b, c)
}

// 全局变量

var a = 1

// 全局变量定义不能使用 :=
// b := 2

// 方便定义多个
var (
b = "abc"
c = 1
d = true
)

func main() {
variableZeroValue()
variableInitialValue()
variableTypeDeduction()
variableShorter()
}

内建变量类型

  • bool, stiring
  • (u)int, (u)int8, (u)int16, (u)int32, (u)int64, uintptr
  • byte, rune
  • float32, float64, complex64, complex128

常量与枚举

package main

import (
"fmt"
"math"
)

func tri() {
a, b := 3, 4
var c int
// 先把 int 转 float64 再转回 int
c = int(math.Sqrt(float64(a*a + b*b)))
fmt.Println(c)
}

// 定义常量
func consts() {

var c int

// 指定类型, 下面需要强转为 float64
// const a, b int = 3, 4
// c = int(math.Sqrt(float64(a*a + b*b)))

// 不指定类型, 不需要强转为 float64
const a, b = 3, 4
c = int(math.Sqrt(a*a + b*b))
fmt.Println(c)

}

// 定义枚举
func enums() {

//const (
// cpp = 0
// java = 1
// python = 2
// golang = 3
//)

// 使用 iota 自增加,与上面一样
const (
cpp = iota
java
python
golang
_ // 跳开 4
javascript
)

fmt.Println(cpp, java, python, golang, javascript) // 0 1 2 3 5

// b, kb, mb, gb, tb, pb
const (
b = 1 << (10 * iota)
kb
mb
gb
tb
pb
)

fmt.Println(b, kb, mb, gb, tb, pb) // 1 1024 1048576 1073741824 1099511627776 1125899906842624
}

func main() {
tri()
consts()
enums()
}

条件语句

package main

import (
"fmt"
"io/ioutil"
)

// if
func read() {
const filename = "abc.txt"

// 读取文件
contents, err := ioutil.ReadFile(filename)
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("%s\n", contents)
}

// 也可以这样写
if contents, err := ioutil.ReadFile(filename); err != nil {
fmt.Println(err)
} else {
fmt.Printf("%s\n", contents)
}
}

// switch
func eval(a, b int, op string) int {
var result int
// switch 会自动 break, 除非使用 fallthrough
switch op {
case "+":
result = a + b
case "-":
result = a - b
case "*":
result = a * b
case "/":
result = a / b
default:
panic("unsupported operator: " + op)
}
return result
}

// switch
func grade(score int) string {

// switch 后面没有表达式
switch {
case score < 0 || score > 100:
panic("wrong score")
case score < 60:
return "E"
case score < 70:
return "D"
case score < 80:
return "C"
case score < 90:
return "B"
case score <= 100:
return "A"
}
return ""
}

func main() {
read()
fmt.Println(eval(1, 2, "+")) // 3
grade(100)
}

循环

package main

import (
"bufio"
"fmt"
"os"
"strconv"
)

// 转为二进制
func convertToBin(n int) string {
res := ""
for ; n > 0; n /= 2 {
lsb := n % 2
res = strconv.Itoa(lsb) + res
}
return res
}

// 打印文件
func printFile(fileName string) {
file, err := os.Open(fileName)
if err != nil {
panic(err)
}

scanner := bufio.NewScanner(file)

for scanner.Scan() {
fmt.Println(scanner.Text())
}

}

// 死循环
func forever() {
for {
fmt.Println("forever")
}
}

func main() {
fmt.Println(
convertToBin(5),
convertToBin(13),
)

printFile("abc.txt");
forever()
}

函数

package main

import (
"fmt"
"math"
)

// 返回多个值
func div(a, b int) (int, int) {
return a / b, a % b
}

// 可以对返回值命名
func div2(a, b int) (q, r int) {
return a / b, a % b
}

// 返回 error
func eval(a, b int, op string) (int, error) {
switch op {
case "+":
return a + b, nil
case "-":
return a - b, nil
case "*":
return a * b, nil
case "/":
return a / b, nil
default:
return 0, fmt.Errorf("unsupported opration: %s", op)
}
}

// 使用函数式编程
func apply(op func(int, int) int, a, b int) int {
return op(a, b)
}

// 可变参数
func sum(numbers ...int) int {
sum := 0
for i := range numbers {
sum += numbers[i]
}
return sum
}

func pow(a, b int) int {
return int(math.Pow(float64(a), float64(b)))
}

func main() {

i, i2 := div(5, 3)
fmt.Println(i, i2)

q, r := div2(5, 3)
fmt.Println(q, r)

res, err := eval(1, 2, "&") // unsupported opration: &
if err != nil {
fmt.Println(err)
} else {
fmt.Println(res)
}

fmt.Println(apply(pow, 2, 2)) // 4

fmt.Println(sum(1, 2, 3, 4)) // 10
}

指针

package main

import "fmt"

// 使用指针
func swap(a *int, b *int) {
*b, *a = *a, *b
}

func swap2(a, b int) (int, int) {
return b, a

}

func main() {
a, b := 3, 4
swap(&a, &b)
fmt.Println(a, b) // 4 3

a, b = 3, 4
a, b = swap2(a, b)
fmt.Println(a, b) // 4 3
}

数组、切片和容器

数组

package main

import "fmt"

// 数组定义
func defineArray() {

// 定义数组的方法
var arr1 [5]int
arr2 := [3]int{1, 3, 5}
arr3 := [...]int{2, 4, 6, 8}

fmt.Println(arr1, arr2, arr3) // [0 0 0 0 0] [1 3 5] [2 4 6 8]

// 定义二维数组
var grid [2][3]int
fmt.Println(grid) // [[0 0 0] [0 0 0]]
}

// 遍历数组
func printArray() {
arr := [...]int{2, 4, 6, 8}

for i := 0; i < len(arr); i++ {
fmt.Println(arr[i])
}

// 通过 range 可以获取下标
for i := range arr {
fmt.Println(arr[i])
}

// 获取下标和值
for i, v := range arr {
fmt.Println(i, v)
}

// 只获取值, 可以使用 _ 来省略变量
for _, v := range arr {
fmt.Println(v)
}

}

// [3]int 和 [5]int 是不同的类型
func printArray2(arr [5]int) {
fmt.Println(arr)
}

// 数组是值类型
func printArray3(arr [5]int) {
arr[0] = 100
fmt.Println(arr) // [100, 0, 0, 0, 0]
}

// 传递指针
func printArray4(arr *[5]int) {
arr[0] = 100
fmt.Println(*arr) // [100, 0, 0, 0, 0]
}

func main() {
defineArray()
printArray()

var arr1 [5]int
// arr2 := [3]int{1, 3, 5}
// arr3 := [...]int{2, 4, 6, 8, 10}

// [3]int 和 [5]int 是不同的类型
printArray2(arr1) // 在函数里面改变数组的值
// printArray2(arr2) // cannot use arr2 (type [3]int) as type [5]int in argument to printArray2

// 在函数里改变了数组第一个值, 后面打印还是不变,每次传递数组都是一个副本
printArray3(arr1)
fmt.Println(arr1) // [0, 0, 0, 0, 0]

// 传递地址过去就会改变
printArray4(&arr1)
fmt.Println(arr1) // [100, 0, 0, 0, 0]

}

切片

package main

import "fmt"

// 切片
func mySlice() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
fmt.Println("arr[2:6] = ", arr[2:6]) // arr[2:6] = [2 3 4 5]
fmt.Println("arr[:6] = ", arr[:6]) // arr[2:6] = [2 3 4 5]
fmt.Println("arr[2:] = ", arr[2:]) // arr[2:] = [2 3 4 5 6 7]
fmt.Println("arr[:] = ", arr[:]) // arr[:] = [0 1 2 3 4 5 6 7]
}

// 更新
func updateSlice(slice []int) {
slice[0] = 2019
}

// 扩展
func extendSlice() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
// 我们知道 s1 只有 4 个元素, 但是 s2 还是能
s1 := arr[2:6]
s2 := s1[3:5]
fmt.Println(s1) // [2 3 4 5]
fmt.Println(s2) // [5 6]
fmt.Printf("len=%d, cap=%d", len(s1), cap(s1)) // len=4, cap=6
}

// 添加
func appendSlice() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}

// 添加元素如果超过了 cap, 系统会重新分配更大的底层数组
// 由于值的传递关系, 必须接受 append 的返回值
s1 := arr[2:6]
s2 := append(s1, 100)
s3 := append(s2, 100)
s4 := append(s3, 100)
s5 := append(s4, 100)
fmt.Println(s1, s2, s3, s4, s5) // [2 3 4 5] [2 3 4 5 100] [2 3 4 5 100 100] [2 3 4 5 100 100 100] [2 3 4 5 100 100 100 100]
}

// 创建 slice
func createSlice() {

// 0. 创建一个空的 slice
var s []int
// 发现 cap 是从 1 2 4 8 16 32... 扩大
for i := 0; i < 100; i++ {
s = append(s, 1+2*i)
printSlice(s)
}

// 1. 创建一个带有值的 slice
s1 := []int{1, 2, 3, 4, 5}
printSlice(s1) // len=5, cap=5, slice=[1 2 3 4 5]

// 2. 创建一个 cap = 16
s2 := make([]int, 16)
printSlice(s2) // len=16, cap=16, slice=[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]

// 3. 创建一个 len = 10, cap = 32
s3 := make([]int, 10, 32) // len=10, cap=32, slice=[0 0 0 0 0 0 0 0 0 0]
printSlice(s3)
}

// 复制
func copySlice() {
src := []int{1, 2, 3}
dst := make([]int, 16)
fmt.Println(dst) // [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
copy(dst, src)
fmt.Println(dst) // [1 2 3 0 0 0 0 0 0 0 0 0 0 0 0 0]
}

// 删除
func deleteSlice() {
// 删除下标为3的元素
s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}
s = append(s[:3], s[4:]...) // s[4:]... 转换为可变参数
fmt.Println(s) // [0 1 2 4 5 6 7 8]

// 删除第一个
s1 := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}
s1 = s1[1:]
fmt.Println(s1) // [1 2 3 4 5 6 7 8]

// 删除最后一个
s2 := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}
s2 = s2[:len(s2) - 1]
fmt.Println(s2) // [0 1 2 3 4 5 6 7]
}

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

func main() {
mySlice()

arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
slice1 := arr[:]
fmt.Println("Before update: ", slice1) // Before update: [0 1 2 3 4 5 6 7]
updateSlice(slice1)
fmt.Println("After update: ", slice1) // After update: [2019 1 2 3 4 5 6 7]

extendSlice()

appendSlice()

createSlice()

copySlice()

deleteSlice()
}

Map

package main

import "fmt"

// 定义 map
func defineMap() {

// 定义一个带默认值的 map
m1 := map[string]string{
"a": "A",
"b": "B",
}

// 定义一个 empty map
m2 := make(map[string]string)

// 定义一个 nil map
var m3 map[string]string

fmt.Println(m1, m2, m3) // map[a:A b:B] map[] map[]

}

// 遍历 map
func traversingMap() {
m := map[string]string{
"a": "A",
"b": "B",
}

// 打印 key value
for k, v := range m {
fmt.Println(k, v)
}

// 只打印 key
for k := range m {
fmt.Println(k)
}

// 只打印 value
for _, v := range m {
fmt.Println(v)
}

}

// 判断是否存在
func containMap() {
m := map[string]string{
"a": "A",
"b": "B",
}

value, ok := m["c"]
if ok {
fmt.Println(value)
} else {
fmt.Println("不存在")
}

if value, ok := m["b"]; ok {
fmt.Println(value)
} else {
fmt.Println("不存在")
}

}

// 删除元素
func deleteMap() {
m := map[string]string{
"a": "A",
"b": "B",
}
fmt.Println(m) // map[a:A b:B]
delete(m, "a")
fmt.Println(m) // map[b:B]
}

func main() {
defineMap()
traversingMap()
containMap()
deleteMap()
}

例题:查找最长不重复子串

package main

import "fmt"

// 查早最长不重复子串
func lengthOfSubString(s string) int {
start := 0
maxLength := 0
lastOccuredMap := make(map[rune]int)

for i, ru := range []rune(s) {
if lastI, ok := lastOccuredMap[ru]; ok && lastI >= start {
start = lastI + 1
}

if i-start+1 > maxLength {
maxLength = i - start + 1
}
lastOccuredMap[ru] = i
}
return maxLength
}

func main() {
fmt.Println(lengthOfSubString("aaa"))
fmt.Println(lengthOfSubString("abab"))
fmt.Println(lengthOfSubString("abc"))
fmt.Println(lengthOfSubString("abcabc"))
}

字符和字符串处理

package main

import "fmt"

func runeTest() {
s := "cuzz是我!"
for i, b := range []byte(s) {
fmt.Printf("(%d %X %c) ", i, b, b)
}

fmt.Println()

for i, u := range s {
fmt.Printf("(%d %X %c) ", i, u, u)
}

fmt.Println()

for i, r := range []rune(s) {
fmt.Printf("(%d %X %c) ", i, r, r)
}

// 输出
// (0 63 c) (1 75 u) (2 7A z) (3 7A z) (4 E6 æ) (5 98 ) (6 AF ¯) (7 E6 æ) (8 88 ) (9 91 ) (10 21 !)
// (0 63 c) (1 75 u) (2 7A z) (3 7A z) (4 662F 是) (7 6211 我) (10 21 !)
// (0 63 c) (1 75 u) (2 7A z) (3 7A z) (4 662F 是) (5 6211 我) (6 21 !)

// 说明 range s 使用的 utf-8 遍历, 但是观察下标发现不是连续的
// ascii 转为 utf-8 如:(4 E6) (5 98) (6 AF) -> (4 662F)
// 使用 []rune() 转换可以使下标连续输出

}

func main() {
runeTest()
}

面向对象

  • go 语言仅支持封装,不支持继承和多态
  • go 语言没有 class,只有 struct

结构体和方法

package main

import (
"fmt"
)

// 定义结构体, 小写对外不可见
type treeNode struct {
value int
left, right *treeNode
}

// setter, 错误, 由于 go 是传值, 不会改变
func (node treeNode) setVal(value int) {
node.value = value
}

func (node *treeNode) setValue(value int) {
node.value = value
}

// 给结构体定义方法 node.print()
func (node treeNode) print() {
fmt.Println(node.value)
}

// 普通的方法 print(node)
func print(node treeNode) {
fmt.Println(node.value)
}

// 定义一个工厂方法
func createNode(value int) *treeNode {
return &treeNode{value: value}
}

// 遍历
func (node *treeNode) traverse() {
if node == nil {
return
}
node.left.traverse()
node.print()
node.right.traverse()

}

func main() {
// 定义一个空的结构体
var node treeNode
fmt.Println(node) // {0 <nil> <nil>}

// 使用构造器定义一个结构体
node2 := treeNode{
value: 1,
left: &treeNode{}, // 取地址
right: new(treeNode), // new() 获取的是地址
}
fmt.Println(node2) // {1 0xc00000c0c0 0xc00000c0a0}

// 使用工厂方法创建
node3 := treeNode{
value: 0,
}
node3.left = createNode(1)
node3.right = createNode(2)
fmt.Println(node3) // {0 0xc00008e0a0 0xc00008e0c0}

// 区别
node.print() // 0
print(node) // 0

// 不会改变, go 是传值
node.setVal(1)
node.print() // 0

// 会改变
node.setValue(1)
node.print() // 1

fmt.Println()

// 中顺遍历 0
// 1 2
node3.traverse() // 1 0 2
}

包和封装

    • 每个目录一个包
    • main 包包含可执行入口
    • 为结构定义的方法必须放在同一包内
    • 可以是不同的文件
  • 封装
    • 一般使用驼峰命名
    • 首字母大写表示 public
    • 首字母小写表示 private

Queue.go

package queue

import "fmt"

type Queue []int

func (q *Queue) Push(v int) {
*q = append(*q, v)
}

func (q *Queue) Pop() int {
head := (*q)[0]
*q = (*q)[1:]
return head
}

func (q *Queue) Head() int {
return (*q)[0]
}

func (q *Queue) IsEmpty() bool {
return len(*q) == 0
}

func (q *Queue) Print() {
for _, v := range *q {
fmt.Print(v, " ")
}
fmt.Println()
}

test.go

package main

import (
"awesomeProject/queue"
"fmt"
)

func main() {

// 定义一个有默认值的队列
q := queue.Queue{1}
q.Push(2)
q.Push(3)
q.Push(4)
q.Print() // 1 2 3 4

fmt.Println(q.Pop()) // 1
q.Print() // 2 3 4

q.Pop()
q.Pop()
q.Pop()

fmt.Println(q.IsEmpty()) // true
}

项目结构

环境变量:

  • GOROOT:go语言自带的类库
  • GOPATH:用户源代码目录
    • src:源文件
    • pkg:build 的之后的中间文件
    • bin:可执行文件

接口

duck typing

  • “像鸭子走路,像鸭子叫…”,那么就是鸭子
  • 描述事物的外部行为而非内部结构
  • 严格说 go 属于结构化类型系统,类似 duck typing

接口定义和实现

定义一个假的发送请求,有一个 Get 方法

package mock

type Retriever struct {
Contents string
}

func (r Retriever) Get(url string) string {
return url + "hi, cuzz..."
}

定义一个真正发送请求,有一个 Get 方法

package work

import (
"net/http"
"net/http/httputil"
"time"
)

type Retriever struct {
UserAgent string
TimeOut time.Duration
}

func (r Retriever) Get(url string) string {
resp, err := http.Get(url)
if err != nil {
panic(err)
}
result, err := httputil.DumpResponse(resp, true)

resp.Body.Close()

if err != nil {
panic(err)
}
return string(result)
}

测试

package main

import (
"awesomego/retriever/mock"
"awesomego/retriever/work"
"fmt"
)

// 定义一个接口
type Retriever interface {
Get(url string) string
}

// 传入接口
func download(r Retriever) string {
return r.Get("http://blog.cuzz.site")
}

func main() {

// 接口定义
// var mockRetriever Retriever
// mockRetriever = mock.Retriever{}


mockRetriever := mock.Retriever{}
fmt.Println(download(mockRetriever))

workRetriever := work.Retriever{}
fmt.Println(download(workRetriever))
}

我们发现在接口是调用放定义的,结构体中的接口也是隐式的,结构体满足接口中的方法,就可以说这个结构体实现了这个接口。

接口的值类型

golang中,接口值是由两部分组成的,一部分是接口的类型,另一部分是该类型对应的值,我们称其为动态类型和动态值。

func main() {

mockRetriever := mock.Retriever{}
fmt.Printf("%T, %v\n", mockRetriever, mockRetriever) // mock.Retriever, {}

workRetriever := work.Retriever{}
fmt.Printf("%T, %v\n", workRetriever, workRetriever) // work.Retriever, { 0s}
}

接口组合

package main

// 定义一个接口
type Retriever interface {
Get(url string) string
}

// 定义另一个接口
type Poster interface {
Post(url string, params map[string]string)
}

// 接口组合
type RetrieverAndPoster interface {
Retriever
Poster
// 也可以定义其他方法
AnotherMethod()
}

func main() {
}

常用系统接口

1、Stringer

Stringer接口中的 string 相当与 Java #toString 方法

package work

import (
"fmt"
"time"
)

type Retriever struct {
UserAgent string
TimeOut time.Duration
}

func (r Retriever) String() string {
return fmt.Sprintf("UserAgent: %v, TimeOut: %v", r.UserAgent, r.TimeOut)
}

测试

package main

import (
"awesomego/retriever/work"
"fmt"
"time"
)


func main() {
workRetriever := work.Retriever{"Mozilla/5.0", time.Minute}
fmt.Println(workRetriever) // UserAgent: Mozilla/5.0, TimeOut: 1m0s
}

2、Reader

type Reader interface {
Read(p []byte) (n int, err error)
}

3、Writer

type Writer interface {
Write(p []byte) (n int, err error)
}

函数式编程

  • 函数是一等公民:参数,变量,返回值都可以是函数
  • 高级函数
  • 闭包
package main

import "fmt"

// 定义一个 adder 函数, 没有参数, 返回值是一个函数
func adder() func(int) int {
sum := 0
return func(v int) int {
sum += v
return sum
}
}

// 定义斐波那契数列
func fibonacci() func() int{
a, b := 0, 1
return func() int {
a, b = b, a + b
fmt.Println(a)
return a
}
}

func main() {
a := adder()
for i := 0; i < 10; i++ {
fmt.Printf("0 + 1 + ... + %d = %d\n", i, a(i))
}

f := fibonacci()
f() // 1
f() // 1
f() // 2
f() // 3
f() // 5
}

资源管理与出错处理

defer 调用

你可以在 Go 函数中添加多个defer语句,当函数执行到最后时,这些 defer 语句会按照逆序执行(即最后一个defer语句将最先执行),最后该函数返回。特别是当你在进行一些打开资源的操作时,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题。如下代码所示,我们一般写打开一个资源是这样操作的:

func CopyFile(dst, src string) (w int64, err error) {
srcFile, err := os.Open(src)
if err != nil {
return
}

defer srcFile.Close()

dstFile, err := os.Create(dst)
if err != nil {
return
}

defer dstFile.Close()

return io.Copy(dstFile, srcFile)
}

错误处理

错误处理是任何语言都需要考虑到的问题,而 Go 语言在错误处理上解决得更为完善,优雅的错误处理机制是 Go 语言的一大特点。

1、error

Go 语言引入了一个错误处理的标准模式,即error接口,该接口定义如下:

type error interface {
Error() string
}

对于大多数函数,如果要返回错误,可以将error作为多返回值的最后一个:

func foo(param int)(ret int, err error) {
...
}

调用时的代码:

n, err := foo(0)
if err != nil {
// 错误处理
} else {
// 使用返回值n
}

2、panic

  • 停止当前函数执行
  • 一直向上返回,执行每一层的 defer
  • 如果没有遇见 recover,程序退出

3、recover

  • 仅在 defer 中调用
  • 获取 panic 的值
  • 如果无法处理,可以重新 panic
package main

import (
"fmt"
)

func tryRecover() {

// 匿名函数里
defer func() {
r := recover()
if err, ok := r.(error); ok {
fmt.Println("Error occurred: ", err)
} else {
panic(fmt.Sprintf("I don't know what to do: %v", r))
}
}()


a := 1
b := 0
fmt.Println(a / b) // runtime error: integer divide by zero

// panic(errors.New("this is an error"))

// panic(123) // 如果不是一个错误的话就, 再次 panic 出去
}

func main() {

tryRecover()

}

并发编程

goroutine

1、协程

  • 轻量级“线程”
  • 非抢占式多任务处理,由协程主动交出控制权
  • 编译器/解释器/虚拟器层面的多任务
  • 多个协程可能在一个或者多个线程上运行
package main

import (
"fmt"
"time"
)

func test() {
// 此时, 不会输出, main 先退出了, 必须让 main sleep
for i := 0; i < 1000; i++ {
// 匿名函数
go func(i int) {
for {
fmt.Printf("From %d\n", i)
}
}(i)
}

time.Sleep(time.Millisecond)
}

func test2() {

// 此时不会退出, 因为不能交出控制权
var arr [10]int
for i := 0; i < 10; i++ {
// 匿名函数
go func(i int) {
arr[i]++
}(i)
}

time.Sleep(time.Millisecond)
}

func main() {
test()
test2()
}

2、go 语言中的调度器

协程可以相互通信

channel

channelgoroutine之间互相通讯的东西。类似我们 Unix 上的管道(可以在进程间传递消息),用来goroutine之间发消息和接收消息。其实,就是在做goroutine之间的内存共享。channel是类型相关的,也就是说一个channel只能传递一种类型的值,这个类型需要在channel声明时指定。

package main

import (
"fmt"
"time"
)

// 定义chan
func defineChan() {
// 声名一个传递int型的channel
// var a chan int

// 初始化一个int型channel
a := make(chan int)

// 从channel中获取
go func() {
for {
z := <-a
fmt.Println(z)
}

}()

a <- 1
time.Sleep(time.Millisecond)
}

// 定义带缓存chan
func bufChan() {

// 初始化一个int型channel
a := make(chan int, 3)

// 从channel中获取
go func() {
for {
//z, ok := <-a
//if !ok {
// break
//}
//fmt.Println(z)

// 或者使用这种, 确保发送完成
for z := range a {
fmt.Println(z)
}
}

}()

a <- 1
a <- 2
a <- 3
a <- 4
close(a) // 关闭了的话, 就一直发送0
time.Sleep(time.Millisecond)
}

// 如何使用
func chanDemo() {
// 定义一个只能收数据的channel, 把数据放到channel中
var channels [10]chan<- int
for i := 0; i < len(channels); i++ {
channels[i] = createWorker(i)
}

// 向channel中写数据
for i := 0; i < len(channels); i++ {
channels[i] <- 'a' + i
}

time.Sleep(time.Millisecond)

}

func createWorker(i int) chan<- int {
c := make(chan int)
go func() {
for {
fmt.Printf("Worker %d received %c\n", i, <-c)
}
}()
return c
}


func main() {
defineChan()
bufChan()
chanDemo()
}

使用 Channel 等待任务结束

package main

import (
"fmt"
)

type worker struct {
in chan int
done chan bool // 使用done来通信确定完成
}

func chanDemo() {
var channels [10]worker

for i := 0; i < len(channels); i++ {
channels[i] = createWorker(i)
}

// 向channel中写数据
for i := 0; i < len(channels); i++ {
channels[i].in <- 'a' + i
<-channels[i].done // 等待channel完成
}

}

func createWorker(i int) worker {
w := worker{
in: make(chan int),
done: make(chan bool),
}
go func() {
for in := range w.in {
fmt.Printf("Worker %d received %c\n", i, in)
w.done <- true
}
}()
return w
}

func main() {
chanDemo()
}

使用 select 进行调度

package main

import (
"fmt"
"math/rand"
"time"
)

func selectDemo() {
var c1, c2 chan int
c1, c2 = createChan(), createChan()
for {
select {
case n := <-c1:
fmt.Printf("from c1, val: %d\n", n)
case n := <-c2:
fmt.Printf("from c2, val: %d\n", n)
}
}
}

func createChan() chan int {
out := make(chan int)
go func() {
i := 0
for {
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
i++
out <- i
}
}()
return out
}

func main() {
selectDemo()
}

Comments