Go基础

特别说明:本笔记基于 Go 1.16 版本,根据 astaxie 的 web 学习教程:build-web-application-with-golang

安装

windows

golang 中国官网 下载,然后正常安装, cmd 检查是否加入系统环境变量中。

基础知识

从 Go 1.12 开始,使用 Go Module 替换了 GOPATH 的用法,大大优化了包管理。这里只介绍最新的 Go Module 模式:

Go Module

开启

默认不开启,查看配置

go env

go env 命令需要 Go版本 >= 1.13

手动开启

go env -w GO111MODULE=on

设置代理

由于科学上网挺麻烦,所以需要先设置本地代理
命令行设置:

go env -w GOPROXY=https://goproxy.io

其他代理地址:

https://goproxy.cn
https://mirrors.aliyun.com/goproxy/

常用命令

初始化

一定要接项目名

go mod init [module 名称]

检查和清理依赖

go mod tidy

安装包

go get -v github/com/go-ego/[email protected]

更新依赖

go get -u

更新指定包依赖:

go get -u github.com/go-ego/gse

指定版本:

go get -u github/com/go-ego/[email protected]

下载依赖

go mod download

转移依赖至本地vendor

go mod vendor

手动修改依赖文件

go mod edit

打印依赖图

go mod graph

校验依赖

go mod verify

replace

go mod edit -replace github.com/go-ego/gse=/path/to/local/gse
go mod edit -replace github.com/go-ego/gse=github.com/vcaesar/gse

作用是将某些不能访问的包,使用本地包文件或者别的包地址替换

GOPATH

用于安装你获取的包,存放的路径: $GOPATH/pkg

工作空间

项目文件源码都是放在 $GOPATH/src/ 中,以文件的方式存在,允许多个文件嵌套归纳,如 github.com/astaxie/beedb

注意:以上是 GOPATH 模式的传统结构,如果使用 Go Module ,则项目不能放在 GOPATH 路径下,否则编译会报错,Go Module 模式下,项目文件可以随意在哪

基本使用

  1. 创建工具项目,源码创建完毕后,执行
go mod init mymath
go mod tidy

这样就创建了一个 Module,会在本地生成 go.mod 依赖文件,可以被其他项目调用。

  1. 创建应用项目

**重要提醒:**在 Goland 设置中,将Go Module 设置中的 Enable Go Modules integration 打开,这样才会有包补全提示

go mod init mathapp
  1. 演示 mathapp/go.mod 本地包导入 replace
require mymath v0.0.0

replace (
	mymath v0.0.0 => ../mymath
)
  1. 编译成,可执行文件
go mod tidy
go build
mathapp/mathapp.exe

提醒新手,这里一定有main.go 文件,并且注意,package main ,不是默认的文件夹名,不然 go build 不会起作用

常用命令

go build

编译代码

  • 普通包,不会产生任何文件,如果需要在 $GOPATH/pkg 下生成对应文件,请使用 go install
  • main 包,执行后会在当前目录生成可执行文件。如果需要在 $GOPATH/bin 下生成相应文件,需要使用 go install
  • 如果目录下存在多个 .go 文件,可以在 go build 后面接文件名来指定,无则默认编译所有 go 文件
  • 会忽略以 _. 开头的 go 文件

go clean

移除当前源码包和关联源码包里面编生成的文件:

_obj/            旧的object目录,由Makefiles遗留
_test/           旧的test目录,由Makefiles遗留
_testmain.go     旧的gotest文件,由Makefiles遗留
test.out         旧的test记录,由Makefiles遗留
build.out        旧的test记录,由Makefiles遗留
*.[568ao]        object文件,由Makefiles遗留

DIR(.exe)        由go build产生
DIR.test(.exe)   由go test -c产生
MAINFILE(.exe)   由go build MAINFILE.go产生
*.so             由 SWIG 产生

一般用于版本控制提交前使用,具体参数:

  • -i 清除关联的安装的包和可运行文件,也就是通过go install安装的文件
  • -n 把需要执行的清楚命令打印出来,但是不执行
  • -r 循环的清除在import中引入的包
  • -x 打印出执行的详细命令,和 -n 的不同是会执行命令

go fmt

格式化,用法 go fmt xx.go ,大部分开发工具都会在保存时自动调用。

go get

动态获取远程代码包,在内部其实分为两步:

  1. 下载源码包
  2. 执行 go install

具体参数:

  • -d 只下载,不安装
  • -u 强制使用网络去更新包和它的依赖包
  • -v 显示执行的命令
  • -t 同时也下载需要为运行测试所需要的包
  • -f 只在包含 -u 参数时有效,不去验证 import 中每一个都已经获取,这对本地 fork 的包特别有用

go install

在内部分为两步:

  1. 生成结果文件(可执行文件或 .a 包)
  2. 把编译好的结果移到 $GOPATH/pkg 或者 $GOPATH/bin

参数:

  • -v 打印具体执行的信息

go test

执行这个命令,会自动读取源码目录下的所有 xxx_test.go 文件,生成并运行测试用的可执行文件。
参数:

  • -v 显示测试的详细命令

go tool

  • go tool fix 用于修复以前的老版本代码至新版本
  • go tool vet directory/files 用来分析当前的代码是否是正确代码

go generate

这个命令是 Go 1.4 之后开始设计的,用于在编译前自动化生成某类代码。

godoc

需要安装:

go get golang.org/x/tools/cmd/godoc

使用:
生成本地在线文档:

godoc -http:localhost:6060

命令行查看:

go doc builtin
go doc net.http	// 查看包
go doc fmt.Printf	// 查看函数
go doc -src fmt.Printf	// 查看源码

go run

运行 main 包程序

语法基础

变量

var num int
var total, num int
var total int = 1000
var total, num int = 1000, 10

var studentNum, className = 52, "高一三班"
studentNum, className := 52, "高一三班"

_ 是特殊的变量名,任何赋予它的值都将被丢弃

a, _ := 3, 9

已申明但是未使用的变量,在编译时会报错

常量

不可改变的值,可以定义为数值、布尔值、字符串等类型

const studentName = "Xiao Ming"
const Pi float32 = 3.1415926

内置基础类型

Boolean

布尔值,类型为 bool ,值为 truefalse ,默认 false

数值

整数

整数类型分为两大类:

  • 无符号(uint)
  • 有符号(int)

所有类型:

  • rune int32
  • int8 byte 字节
  • int16
  • int64
  • uint8
  • uint16
  • uint32
  • uint64

注意:

  1. 这里的 8 16 都是指的位数,即 bit ,一个字节是 1 byte = 8 bit ,所以 int8byte 是同个意思;
  2. 定义的整数默认是 int 类型,但和 int32 不是同一种类型,所有不同类型的整数之间是不能进行运算的,不然编译会报错,例如:
func main() {
	var num1 int8 = 12
	var num2 int32 = 2333
	numSum := num1 + num2
}

// invalid operation: num1 + num2 (mismatched types int8 and int32)

浮点数

浮点数分为两种:

  • float32
  • float64 (默认)

复数

默认类型是 complex128 ,64位实数+64位虚数,其他类型有: complex64

var c complex64 = 5+5i
fmt.Printf("Value is: %v", c)

字符串

类型为 string ,可以通过双引号 "" 或者单引号 '' 来定义。

var studentName string = "Xiao Hong"
var className string = '初三二班'

字符串可以像数组一样进行切片获取

hostName := "www.rungolf.com"
fmt.Println(hostName[4:11])
// rungolf
fmt.Println(hostName[4:])
// rungolf.com
fmt.Println(hostName[:11])
// www.rungolf

字符串是不可变值,即使它可以被切片获取,但是不能进行赋值操作

var hostName string = "www.rungolf.com"
fmt.Println(string(hostName[0]))
// w

hostName[0] = 'h'
// cannot assign to hostName[0] (strings are immutable)

如果非要修改,则可以通过转为 []byte ,通过数组修改后再转回来。
字符串可以通过 + 连接,这和大部分语言一致:

firstName := "L"
secondName := "W"
myName := firstName + secondName

多行字符串

rows := `hello
	world`

错误类型

类型为 error ,Go 中专门有一个处理错误的包 errors

func main() {
	err := errors.New("This is a customer error defind!")
	if err != nil {
		fmt.Println(err)
	}
}

array

数组

var arr [n]type
var arr [2]int
arr[0] = 1
arr[1] = 2

数组是不能改变长度的,数组之间的赋值是值的赋值,像整数一样,而不是引用地址赋值,当作为形参传入函数时,是数组的副本,而不是传入指针。
**
初始化方式定义数组:

arr1 := [3]int{1, 2, 3}
// 部分有初始化值
arr2 := [5]int{1, 11}
// 自动根据初始化个数计算数组长度
arr3 := [...]int{1, 12, 113}

多维数组定义:

arr := [2, 3]int{{1, 2, 3}, {11, 22, 33}}

slice

切片,相比较 array ,长度是可变的动态数组,它是引用类型

// 1.
var s []int
// 2.
s := []int{1, 2, 4}

注意 slice 是一个指向底层 array 的引用,即它的值其实是一个指针,当长度动态变化时,他会指向新的一个 array
从概念上来说 slice 更像是一个结构体,它的结构由三部分组成:

  • 一个指针,指向数组中, slice 开始的位置
  • 长度
  • 最大长度, slice 从开始位置到指向的数组末端的长度
arr := [6]int{1, 2, 3, 4, 5, 6}
s := arr[1:3]

以上例子 s 的打印值为 [2, 3] ,s 的长度为 2, s的最大长度 cap 为 5。

手动设置最大长度:

s := arr[1:3:4]	// 第三位表示最大容量计算结束位置

最大容量为 4-1 = 3

map

字典,格式 map[indexType]valueType ,示例:

var numbers map[string]int
numbers := make(map[string]int)

注意:

  1. map 是无序的,即每次打印出来都可能不一样,因为他的下标是无序的;
  2. map 也是可变长度,即和 slice 一样是引用类型;
  3. map 和其他基本类型不同,它不是线程安全,在使用 go-routine 存取时,需要使用 mutex lock 机制;
rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2}
csharpRating, ok := rating["C#"]
if ok {
    fmt.Println("C# is in the key list of rating")
}else {
    fmt.Println("is not in")
}
// 删除元素
delete(rating, "C")

make/new

make 是用于内建类型( map slice channel ) 的内存分配, new 用于各种类型的内存分配。

new(T) 分配了一个零值填充额 T 类型的空间,并返回其地址,即一个 *T 类型的值,也是通常意义上说的指针。
make(T, args) 是返回一个有初始值(非零)的 T 类型,原因是指向数据结构的引用在使用前必须初始化,返回一个初始化后的(非零)的值。

流程控制

if

if x < 10 {
    fmt.Println("x is less than 10")
}

附带局部变量定义

if x := computeVaule(); x < 10 {
    fmt.Println("x is less than 10")
}
fmt.Println(x)
// 最后的打印会报错,因为x只在流程控制局部内有效,是一个局部变量

这里的局部变量声明可以减少内存的消耗,流程结束局部变量占用的内存就会被销毁

goto

谨慎使用,会导致执行顺序变得很复杂

func myFunc() {
	i := 0
    Here:
    println(i)
    i++
    goto Here
}

标签名大小写敏感

for

Go 的for非常灵活,可以简略到和 while 一样的写法

sum := 0
for index := 0; index < 10; index ++ {
    sum += index
}
fmt.Println("index sum is ", sum)
// index sum is  45
index := 0
for ; index < 10;  {
    index ++
}
index := 0
for index < 10 {
    index ++
}

配合 range 读取 slicemap

s := []int{1, 2, 3, 4}
for k, v := range s {
    fmt.Println(k, v)
}
// 0 1
// 1 2
// 2 3
// 3 4

switch

i := 10
switch i {
    case 1:
    	fmt.Println("i is 1")
    case 2, 4, 10:
    	fmt.Println("i is in 1, 2, 10")
    default:
    	fmt.Println("i is not know")
}

注意几个不同的地方:

  • case 语句结束后,默认会 break 不会往下执行,如果需要往下,则使用 fallthrough
  • case 后面可以用逗号分隔,同时判断几个值

函数

通过 func 开头来定义

需要注意的几点:

  1. 可以省略返回返回变量,只定义返回类型,这样就需要 return 具体值,而定义了返回变量,只需要 return 关键字就行
func helloWorld() (output string) {
	output = "hello world"
	return
}
  1. 如果只有一个返回值,并且只定义返回类型,不定义返回变量时,可以省略返回参数的外括号 ()
func helloWorld() string {
	return "string"
}
  1. 多参数,当相同类型时,可以省略前者类型,如果有多个不同类型的参数,则省略的类型向右按离它最近的取
func helloWorld(a, b int) string {
}

func helloWorld(a string, b, c int) string {
}
// 这里 b 为 int

变参

即个数不定的参数,接收到的是一个 slice

func helloWorld(arg ...int) string {
    return reflect.TypeOf(arg).Kind().String()
}
// slice

上面有介绍到一种变量类型的判断,再介绍一种用法 var.(type)

func helloWorld(args ...interface{}) {
	for _, arg := range args {
		switch arg.(type) {
		case int:
			fmt.Println(arg, "is an int value.")
		case string:
			fmt.Println(arg, "is a string value.")
		case int64:
			fmt.Println(arg, "is an int64 value.")
		default:
			fmt.Println(arg, "is an unknown type.")
		}
	}
}

注意:这里的 args 必须使用 interface{} 类型,因为 arg.(type) 对变量 arg 的要求必须是 interface{} 类型。这种使用方法仅限于配合 switch 使用,其他地方不起作用。

和上方这种方法相似的还有另一种单个判断类型的:

value, ok := element.(T)

注意:这里的 element 必须是 interface{} 类型,例如:

func main() {
	var element interface{}
	element = []int{1, 2, 3}
	sValue, ok := element.([]int)
	fmt.Println(sValue, ok)
}

传值vs传指针

  1. 传值:即复制一份值传递过去,函数中改变该副本,不会影响原来的值
  2. 传指针:将值的内存地址(指针)传递过去,则修改指针地址的值,会影响原有变量。
  • 取指针: &x
  • 函数接收表示指针类型: *x
  • Go 中 slice map channel 的实现机制类似指针,所以在传递时可以不用进行取指针操作,但是注意 slice ,如果想在函数中修改 slice 的长度,依然需要取指针传递

defer

当函数执行到最后结束前,会按从后往前的顺序执行 defer ,常用于资源的关闭。

func hasRecords() bool {
    f := file.Open("file Path")
    defer f.Close()
    if xxxx {
        return false
    }
    return true
}

函数作为变量/类型

函数也可以通过 type 用变量来表示

func main() {
	type funcName func(input int)(output string)
}

当其他定义的函数输入和输出类型、数量和 funcName 一致时,则默认它属于 funcName 类型,则在可根据情况传递不同的 funcName 类型变量,不用在传递后再判断一次情况。

package main

import (
	"fmt"
	"reflect"
)

type funcName func(x interface{}) bool

func main() {
	userName := "lw"
	if checkFormat(userName, isString) {
		fmt.Println("user name pass")
	} else {
		fmt.Println("user name is wrong format")
	}
	verifyCode := 212123
	if checkFormat(verifyCode, isInt) {
		fmt.Println("verify code pass")
	} else {
		fmt.Println("verify code is wrong format, only number")
	}
}

func isInt(x interface{}) bool {
	return reflect.TypeOf(x).Kind().String() == "int"
}

func isString(x interface{}) bool {
	return reflect.TypeOf(x).Kind().String() == "string"
}

func checkFormat(x interface{}, f funcName) bool {
	return f(x)
}

这个举例不是很好,但可以参考使用方法

panic&recover

注意:你应当把这当做最后的手段,应该尽可能的减少使用它,虽然它很强大

  1. panic 是一个内建函数,可以中断原有的控制流程,当函数发生 panic 中断,会继续调用延迟函数,然后返回至上一级函数,这个过程不断重复,直至整个程序退出。 panic 除了手动调用,也能是编译错误时触发,例如数组越界访问
  2. recover 是一个内建函数,可以恢复 panic 中断的 goroutine ,并获得 panic 时的输入值。注意:仅在延迟函数中有效,程序正常执行时调用返回 nil ,没有其他任何效果。
defer func(){
	if x := recover(); x != nill {
		// ...
	}
}()
// 注意匿名函数最后需要()来表示执行

main()&init()

区别

  • main() 只能应用于 package main ,必须
  • init() 可以应用于所有 package ,非必须

共同点

  • 不能有任何参数和返回值

注意:

  • 同一个 package 可以存在多个init() ,但是为了代码可阅读性,建议只写一个

一个正常的程序执行过程:

  1. main 程序开始由上至下执行,导入依赖的包,同一个包只会导入一次,不断嵌套
  2. 依赖包先进行常量和变量的初始化,然后执行 init() ,如果有的话。
  3. 所有依赖包导入完成,再进行 main 程序的常量和变量初始化,执行 init() ,最后执行 main()

import

一些省略写法:

  1. 省略包名,可以直接使用函数
import (
	. "fmt"
)

Println("省略包名的写法")
  1. 别名
import(
	f "fmt"
)

f.Println("包的别名")
  1. 引入但是不调用函数
import(
	"fmt"
    _ "database/sql"
)

引入包,但是不加载里面的函数,即主程序中无法使用该包的函数,但是会执行包的初始化函数 init()

struct

结构体

type Person struct {
    name string
    age int
}

var p Person
p.name = "Xiao Ming"
p.age = 23

fmt.Println("The person`s name is", p.name)

其他声明使用方式:

  1. 使用 :=
p := Person{name: "Tom", age: 22}
  1. 使用 new
p := new(Person)

这里返回的是指针类型 *Person ,具体和 make 区别查看 【基础数据类型】中两者专题的具体说明。

匿名字段(嵌入字段)

可以理解为隐式的继承关系,有点类似 php 中的 trait 用法,但实际关系是继承关系。

type Human struct {
    name string
    age int
    birthday string	// 农历生日
}

type Student struct {
    Human
    school string
    birthday string // 阳历生日
}

xm := Student{Human: Human{name: "XiaoMing", age: 14}, school: "一中"}
fmt.Println(xm.name, xm.age, xm.school)

xm.birthday = "2001/8/2"
xm.Human.birthday = "2001/6/23"
fmt.Println(xm.birthday, xm.Human.birthday)

method

结构体除了有常量和变量之外,也有方法

func (r ReceiverType) funcName(parameters) (results)

使用例子:

package main

import (
	"fmt"
	"math"
)

type Rectangle struct {
	width float64
	height float64
}

func (r Rectangle) area() float64 {
	return r.width * r.height
}

type Circle struct {
	radius float64
}

func (c Circle) area() float64 {
	return c.radius * c.radius * math.Pi
}

func main() {
	r := Rectangle{width: 23.2, height: 13.5}
	fmt.Println("the area of rectangle is", r.area())
	c := Circle{radius: 2.5}
	fmt.Println("the area of circle is", c.area())
}

并不像其他语言一样,都会用大括号括起来,所以我个人认为阅读时其实会造成一定的困难

注意:这里的 (c Circle) 都是使用的值传递,传递的都是结构体的复制副本,修改它的字段值,并不会影响实例的,如果需要修改,则需要传递指针,例如:

package main

import (
	"fmt"
	"math"
)

type Circle struct {
	radius float64
	backgroundColor string
}

func (c Circle) area() float64 {
	return c.radius * c.radius * math.Pi
}

func (c *Circle) hover() {
	c.backgroundColor = "#0062B7"
}

func main() {
	c := Circle{radius: 2.5, backgroundColor: "#ffffff"}
	c.hover()
	fmt.Println("circle background color at hover status is", c.backgroundColor)
}

继承

method 也可以和常量变量一样被继承后调用

重写

如果函数名、参数、返回值都一样,就默认重写了,和大部分语言一样

interface

接口,是一组 method 签名的组合,定义对象的一组行动。

任意的类型都实现了空 interface{}

注意: interface 可以被定义变量,但是不能实例化,它能存实现了它本身的类的对象:

type Human struct {
    name string
    age int
}

func (h Human) speak() string {
    fmt.Println("speaking")
}

type Communicate interface {
    speak() string
}

嵌入

interface 也可以像 struct 一样,直接被另一个对象隐式继承

type Communicate interface {
    speak() string   
}

type Student struct {
 	Communicate   
}

reflect

反射,一般分为三步:

  1. 将要反射的值转为 reflect 对象,有两种对象:
t := reflect.TypeOf(i)	// 得到类型的元数据,通过t我们能获取类型定义里面的所有元素
v := reflect.ValueOf(i)	// 得到实际的值,通过v我们获取存储在里面的值,还可以去改变值
  1. 获取对象的一些值:
// 获取定义在 struct 里的标签
tag := t.Elem().Field(0).Tag
// 存储在第一个字段里的值
name := t.Elem().Field(0).String()

获取值返回相应的类型和数值

var x float64 = 3.456
rxv := reflect.ValueOf(x)
fmt.Println("reflect x Type is", rxv.Type())
fmt.Println("reflect x is float64:", rxv.Kind() == reflect.Float64)
fmt.Println("reflect x value is:", rxv.Float())
  1. 修改对象的值,需要传入指针
var x float64 = 3.44
rxv := reflect.ValueOf(&x)
rxv.Elem().SetFloat(5.55)
fmt.Println(x)

这里只是介绍了 reflect 的基本使用

并发

goroutine

它是协程,比线程更小,是通过 Go 的 runtime 管理的一个线程管理器,通过 go 关键字实现。

go sayHello(somebody)

runtime.Gosched() 表示把 CPU 计算资源让给别人,下次某时候继续恢复执行。
Go 1.15 之后,并发线程个数 runtime.GOMAXPROCs ,已经由原来的默认 1 变为 CPU 核心数

channels

通道,用于 多个goroutine之间的通信,每个通道只可以定义一种数据格式,可以发送和接收数据, 必须使用 make 创建

ci := make(chan int)

发送和接收 <-

// 发送v到ch通道
ch <- v
// 接收通道数据并赋值给v
v := <- ch

channel 是阻塞型的,即当有数据在通道中传送时,通道不能再发送另一份数据,必须等到第一份数据被接收,通道空闲后才能使用

func sum(arr []int, c chan int) {
	total := 0
	for _, v := range arr {
		total += v
	}
	c <- total
}

func main() {
	i := []int{1, 2, 3, 4, 5, 56, 6, 6, 3}
	c := make(chan int)
	go sum(i[:4], c)
	go sum(i[4:], c)
	x, y := <-c, <-c
	fmt.Println(x, y, x+y)
}

buffered channels

上面介绍的是阻塞型的通道,也有缓存型的通道,允许同时传递多组数据

make(chan type, n)
c := make(chan int, 2)
c <- 1
c <- 2
fmt.Println(<- c, <- c)

range & close

通过循环直接读取缓存型通道的多个数据

func split(n int, c chan int) {
	for i := 0; i < n; i++ {
		c <- i
	}
	close(c)
}

func main() {
	c := make(chan int, 10)
	go split(11, c)
	for i := range c {
		fmt.Println(i)
	}
}

注意: channel 并不需要像文件一样一定关闭,除非你确定不再需要发送数据了,或者想要显示的关闭通道,关闭通道应该在数据生产的地方关闭,而不应该在接受端,即应用数据端关闭。

v, ok := <- c

可以通过这种方式判断 ok 来测试通道是否关闭

select

用于监听多通道,阻塞型,只有当有数据时才会调用执行,随机选择准备好的通道进行执行

package main

import "fmt"

func producer(c, quit chan int) {
	x, y := 1, 1
	for {
		select {
		case c <- x:
			x, y = y, x + y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}

func main() {
	c := make(chan int)
	quit := make(chan int)
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println("c data:", <-c)
		}
		quit <- 0
	}()
	producer(c, quit)
}

注意:

  • 这里的 select 用法和 switch 差不多,也有 default 只不过如果是当 case 都无数据需要发送、接受,它就会执行
  • 因为是随机执行,所以这里 c 通道发送第11次时阻塞
send c: 1
receive c: 1
receive c: 1
send c: 1
send c: 2
receive c: 2
receive c: 3
send c: 3
send c: 5
receive c: 5
receive c: 8
send c: 8
send c: 13
receive c: 13
receive c: 21
send c: 21
send c: 34
receive c: 34
receive c: 55
send c: 55
quit

注意看这里的运行结果,会非常有意思的是,他并不是我们想的那种,一发一收,而是很可能两发或者两收,我们定义的却并不是 buffered channel ,所以应该是和多线程调用有关,普通通道肯定是阻塞等待的,但是打印可能会因为多线程切换和有先后。

超时

当所有线程都不动等待时,为了防止假死,会通过 select case 设置 time.After

c := make(chan int)
to := make(chan bool)
go func() {
    for  {
        select {
        case v := <- c:
            fmt.Println(v)
        case <- time.After(5* time.Second):
            fmt.Println("timeout after 5 second")
            to <- true
        }
    }
}()
<- to

runtime

Goexit

退出当前 goroutinedefer 函数还是会执行

Gosched

让出当前 goroutine 执行权,调度器安排其他任务,并在下次某个时候从该位置恢复执行

NumCPU

返回 CPU 核心数量

NumGoroutine

返回正在执行和排队的任务总数

GOMAXPROCES

设置可以并行计算的 CPU 最大核数,并返回之前的值

你可能感兴趣的:(一起学Go,golang,go,编程语言,基础教程)