Golang 基础学习

  • 使用vscode开发配置国内源
  • dep包管理器
  • 变量
  • 常量
  • 基本数据类型
    • 判断数据类型
    • 基本数据类型转换
    • 整型
    • 浮点型
    • 布尔类型
    • 字符串(两种)
  • array数组与切片
    • 数组定义及常用方法
    • 数组操作(转切片)
    • 数组遍历
  • map字典
    • map定义及常用方法
    • map遍历
  • 指针
  • 流程控制
    • 运算符
    • if
    • switch case
    • defer延时执行
    • for循环
    • goto跳转语句
  • 函数
    • 内置函数
    • 函数定义
    • 函数几种定义方法
    • init函数
    • 匿名函数和闭包
    • 回调函数(函数作为值)
    • 递归函数(函数自己调用自己)
  • struct 结构体类型
  • interface 接口类型
    • 接口定义
    • 接口案例
  • package包
    • 1.首字母大写外部可见,小写只能在当前包内用,如
    • 2.包import导入的几种类型及ini()自动初始化调用
  • Goroutine并发编程
    • goroutine与线程
      • 可增长的栈内存
      • goroutine调度
    • 通过sync.WaitGroup主动监听goroutine并发执行状态
    • 通过channel实现Goroutine之间通信完成一组任务
  • 小技巧
    • 处理错误errors和fmt.Errorf
    • 比较两个字符串相等
    • 比较两个slice相等(map、array类型)
    • 判断变量类型reflect.TypeOf(变量名)

go run main.go
go build && ./main
go语言类似面向对象语言又有区别,没有继承概念,通过函数组合来简化代码开发和复用。

使用vscode开发配置国内源

go env -w GOPROXY=https://goproxy.cn,direct

dep包管理器

# 安装
brew install depmkdir $GOPATH/src/test
cd  $GOPATH/src/test# 初始化后会生成下列3个文件/目录,vendor里的依赖优先加载。
dep init
├── Gopkg.lock # 自动生成的文件
├── Gopkg.toml # 定义直接依赖项
└── vendor  # 这个目录的依赖代码是优先加载的,类似 node 的 node_module 目录。# 依赖管理帮助
dep help ensure
# 添加一条依赖
dep ensure -add github.com/bitly/go-simplejson
# 这里 @= 参数指定的是 某个 tag
dep ensure -add github.com/bitly/go-simplejson@=0.4.3
# 添加后一定记住执行 确保 同步
dep ensure
# 建议使用
dep ensure -v
#  删除没有用到的 package
dep prune -v

变量

变量定义
var varname type = value
var varname1, varname2 type = v1, v2
varname := value (只允许在func函数内定义)

特殊:_(下划线) 任何赋予它的值都会被丢弃,
例:变量varname赋值20成功,10被丢弃

_, varname := 10, 20

常量

所谓常量,也就是在程序编译阶段就确定下来的值,而程序在运行时无法改变该值。
例:

v2 := 10
const i = 1000
i = v2 // 当对常量赋值时,会发现编辑器提示报错

基本数据类型

判断数据类型

fmt.Printf("%T", 1.121) // 打印数据类型

基本数据类型转换

 //1、string到intint, _ := strconv.Atoi(string2)fmt.Printf("%T %d", int, int)//2、string到int64int64, _ := strconv.ParseInt(string2, 10, 64)fmt.Printf("\n%T", int64)////3、int到stringstring := strconv.Itoa(int)fmt.Printf("\n%T", string)////4、int64到stringstringA := strconv.FormatInt(int64, 10)fmt.Printf("\n%T", stringA)////5、字符串到float64var string4 = "haha"float64, _ := strconv.ParseFloat(string4, 32)fmt.Printf("\n%T", float64)//6、int64转int//intb := int(int64)//fmt.Printf("%T", intb)////7、int转int64//int64:=int64(int)

整型

uint:正整数
int:所有整数

 var num1 uintvar num2 intnum1 = 255num2 = -1fmt.Println(num1, num2)

浮点型

 var num3 float64num3 = 3.141592654

布尔类型

 var bool1 boolbool1 = false

字符串(两种)

  1. uint8类型( byte 型),代表了ASCII码的一个字符。
  2. rune类型,代表一个 UTF-8字符(处理中文)。

1.字符串默认不可变,若想变,需要转为为[]byte类型或rune类型

 s := "hello"c := []byte(s)  // 英文字符串 s 转换为 []byte 类型(c[0] = 'c's2 := string(c)  // 再转换回 string 类型fmt.Printf("%s\n", s2)s3 := "12哈哈"rune2 := []rune(s3) // 中文使用[]rune)rune2[0] = '哈'fmt.Println(string(rune2))

2.使用+操作符来连接两个字符串

3.切片操作–同python

4.多行字符串使用 ``
res := 111 222 333

使用strings库处理字符串:https://studygolang.com/articles/5769
例:

 s := "11 22"fmt.Println(strings.Contains(s, "11"))

5.强制类型转换
int()
float64()
string()

array数组与切片

1.必须指定元素的类型
2.[…] 根据初始值自动推断数组长度是多少

数组定义及常用方法

len(arr):获取长度
cap(arr):获取容量

// 数组定义
func main() {// 必须指定元素的类型和容量// 长度是数组类型的一部分,a1和a2var a1 [3]bool // [true false ture]var a2 [4]bool //// %T元素类型fmt.Printf("%T %T\n", a1, a2)// 数组初始化,默认是零值(布尔:false,整形和浮点型:0,字符串:"")fmt.Println(a1, a2)// 初始化1a1 = [3]bool{true, true, false}fmt.Println(a1)// 初始化2: ... 根据初始值自动推断数组长度是多少a100 := [...]int{1, 1, 3}fmt.Println(a100)// 初始化3: 根据索引初始化a3 := [5]int{0: 1, 4: 2}fmt.Println(a3)// 初始化4: newvar d = new([10]int)d[3] = 100// 数组是值类型b1 := [3]int{1, 2, 3}b2 := b1b2[0] = 100fmt.Println(b1, b2, b1)
}

数组操作(转切片)

数组没有append方法,需要先将数组转化为切片,对切片进行操作

func main() {// 数组没有append方法,需要先将数组转化为切片,对切片进行操作a := [3]int{0, 1, 2}cl := a[:]fmt.Println(cl)cl = append(cl, 3, 4)// 当容量(cap)不足时,切片会在上一个数组长度(len)基础上成倍扩充容量fmt.Println(len(cl), cap(cl)) // 当有5个元素时,此时cap是6,cl = append(cl, 5, 6)fmt.Println(cl)fmt.Println(len(cl), cap(cl)) // 当有7个元素时,超过原来的cap,则此时cap是12/*[0 1 2]5 6[0 1 2 3 4 5 6]7 12*/
}

初始化切片

func main() {a := [3]int{0, 1, 2}cl := a[:]cl1 := a[2:]fmt.Println(cl1)cl = append(cl, 10)fmt.Println(cl)// copy(dest, src) 将源切片中的元素复制到目标切片中(从index 0 开始覆盖)copy(cl, cl1)fmt.Println(cl)// 1、新建一个空切片var aa []int// 2、使用make新建一个切片,必须指定长度aaa := make([]int, 5)fmt.Println(aa, aaa)/*[2][0 1 2 10][2 1 2 10] 可以看到从index 0 开始覆盖[] [0 0 0 0 0]*/
}

数组遍历

// 数组遍历citys := [...]string{"beijing", "shanghai", "shenzhen"}// 1.根据索引遍历for i := 0; i < len(citys); i++ {fmt.Println(citys[i])}// 2.for range 遍历for i, v := range citys {fmt.Println(i, v)}// 2维数组定义[[1,2] [3,4] [5,6]],// 最外层可以写...,即a11 = [...][2]int{}var a11 [3][2]inta11 = [3][2]int{[2]int{1, 2},[2]int{3, 4},[2]int{5, 6},}fmt.Println(a11)for _, v := range a11 {fmt.Println(v)for _, v2 := range v {fmt.Println(v2)}}

map字典

map定义及常用方法

func main() {// map是引用类型,必须初始化,默认是nil。map[key类型]value类型// map[key的数据类型]value的数据类型// 初始化1m := make(map[int]string)m[1] = "dog"fmt.Println(m)// 初始化2m1 := map[string]string{"1": "true","2": "false",}fmt.Println(m1)// 初始化3(较为麻烦,需要两步)var m2 map[string]stringm2 = map[string]string{}m2["name"] = "lisi"m2["age"] = "18"fmt.Println(m2)// 初始化4 (终极大招,使用interface可以接收任意value类型)m3 := map[int]interface{}{}m3[1] = 1m3[2] = falsem3[3] = "str"fmt.Println(m3)// 删除map元素delete(m3, 1)// 查看元素在map中是否存在, map有两个返回值,第二个返回值,如果不存在key,那么ok为false,如果存在ok为truecaptial, ok := m3[3]if ok {fmt.Println("exist is:", captial)} else {fmt.Println("not exist")}/*map[1:dog]map[1:true 2:false]map[age:18 name:lisi]map[1:1 2:false 3:str]exist is: str*/
}

map遍历

 // 使用range遍历,如果是集合,返回 key 和 valuefor key := range testMap {fmt.Println(key, ":", testMap[key]) //打印 k,v}for k, v := range testMap {fmt.Println(k, v)}

指针

&符号取地址,*符号根据地址取值

func main() {//指针--获取变量内存地址a := "aaa"fmt.Println("a 的内存地址: ", &a, "; a的原值: ", a)//指针--通过内存找到原值b := &a//对内存地址赋值*b = "bbb"fmt.Println("b 的内存地址: ", b, "; b的原值: ", *b, "; a的新值: ", a)//指针--空指针判断var p *intif p != nil {fmt.Println("p is null")} else if p == nil {fmt.Println("p =", p)}
}
func showMemAddr(i *int) {fmt.Println(i)  // 输出内存地址fmt.Println(*i) // 输出原值return
}
func main() {i := 1showMemAddr(&i)fmt.Println(&i) // 输出内存地址}

流程控制

运算符

比较运算符(操作的数据类型必须相同)
==
!=
>
<
<=
>=算数运算符(加减乘除余)
+-*/%逻辑运算符
&& 与:两个条件是否都为true
|| 或:两个条件至少一个为true
! 非:条件为false

if

 if x := aaa; x > 10 {fmt.Println(">10")} else if x == 10 {fmt.Println("=10")} else {fmt.Println("<10")}

switch case

// 常规案例
i := 10
switch i {case 1:fmt.Println("i is equal to 1")
case 10, 11, 22:fmt.Println("i is equal to 10 or 11 or 22")
default:fmt.Println("All I know is that i is an integer")
}
// 使用运算符
age := 20switch {case age < 25:fmt.Println("<25")case age > 25:fmt.Println(">25")default:fmt.Println("nono")}

defer延时执行

1.在defer后指定的函数会在函数退出前调用,常用:资源清理、文件关闭、解锁、记录时间等

 func ReadWrite() bool {file.Open("file")defer file.Close() // 只需调用一次,省事if failureX {return false}if failureY {return false}return true}

2.defer采用后进先出模式

 for i:=0; i<5; i++ {defer fmt.Printf("%d",i) // 43210}

for循环

 // 1. 常规for循环,有限制条件count := 10for i := 0; i < count; i++ {fmt.Println(i)}// 2. 无限循环,等价于while Truefor {fmt.Println(1)}// 3. 对arr和map遍历使用for ... rangefor i, n := range numbers {fmt.Printf("index %d\n", i) fmt.Printf("valuse %d\n", n)}

goto跳转语句

func main() {a := 0
A:for a < 10 {a++fmt.Println(a)if a == 10 {break Agoto B}}
B:fmt.Println("我是B")
}
/*
1
2
3
4
5
6
7
8
9
10
我是B
*/

函数

内置函数

append          -- 用来追加元素到数组、slice中,返回修改后的数组、slice
close           -- 主要用来关闭channel
delete          -- 从map中删除key对应的value
panic           -- 停止常规的goroutine  (panic和recover:用来做错误处理)
recover         -- 允许程序定义goroutine的panic动作
imag            -- 返回complex的实部   (complex、real imag:用于创建和操作复数)
real            -- 返回complex的虚部
make            -- 用来分配内存,返回Type本身(只能应用于slice, map, channel)
new             -- 用来分配内存,主要用来分配值类型,比如int、struct。返回指向Type的指针
cap             -- capacity是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)
copy            -- 用于复制和连接slice,返回复制的数目
len             -- 来求长度,比如string、array、slice、map、channel ,返回长度
print、println   -- 底层打印函数,在部署环境中建议使用 fmt 包

函数定义

func 函数名(参数)(返回值){函数体
}

函数几种定义方法

package mainimport "fmt"// 1.有参数有返回值
func f0(x int, y int) (res int) {// 法1return x + y// 法2// res = x + y// return  // 可以省略返回值,当然也可以写上
}// 2.无参数和返回值
func f1() {fmt.Println("f1")
}// 3.无返回值
func f2(x int, y int) {fmt.Println(x + y)
}// 4.无参数有返回值
func f3() int {// 法1// return 3// 法2res := 3return res
}// 5.1.多个返回值,用时变量数量需要与返回值数量一致,不需要可以用_占位,如:s41, s42, _ := f4()
func f4() (int, string, int) {return 1, "dsd", 3
}// 5.2.多个返回值
func f7(x int, y int) (sum int, sub int) {sum = x + ysub = x * y// fmt.Println(sum, sub)return
}// 6.参数类型可简写
func f5(x, y, z int, m, n string, i, j bool) int {return x + y
}// 7.可变长参数... ,注意只能放到最后
func f6(x string, y ...int) {fmt.Println(x)fmt.Println(y) // y的类型是切片 [] int
}// 8.函数作为参数
func add(x, y int) int {return x + y
}
func sub(x, y int) int {return x - y
}
func calc(x, y int, op func(int, int) int) int {return op(x, y)
}func main() {s := f0(3, 5)fmt.Println(s)f1()f2(1, 3)s3 := f3()fmt.Println(s3)s41, s42, s43 := f4()fmt.Println(s41, s42, s43)s5 := f5(2, 2, 2, "m", "n", false, false)fmt.Println(s5)f6("呵呵呵", 1, 2, 3)fff, ggg := f7(3, 3)fmt.Println(fff, ggg)ret2 := calc(10, 20, add)fmt.Println(ret2)
}

init函数

程序启动自动调用,与python类似

匿名函数和闭包

匿名函数:常用于回调函数和闭包

// 9.闭包=函数+引用环境
func adder() func(int) int {var x intreturn func(y int) int {x += yreturn x}
}func main() {// 匿名函数1.保存到变量add := func(x, y int) {fmt.Println(x + y)}add(3, 4)// 匿名函数2.()直接执行func(x, y int) {fmt.Println(x, y)}(20, 10)// 闭包:变量bibao是一个函数并且它引用了其外部作用域中的x变量,此时在bibao的生命周期内,*变量x一直有效*。var bibao = adder()fmt.Println(bibao(10)) // 10fmt.Println(bibao(20)) // 30fmt.Println(bibao(30)) // 60
}

回调函数(函数作为值)

type typeName func(input1 inputType1 , input2 inputType2 [, …]) (result1 resultType1 [, …])

type Callback func(x,y int) int// 说白了就是提供一个接口,通过外部函数实现具体方法
func test(x, y int, callback Callback) int {return callback(x,y)
}
func add(x, y int) int {return x + y
}
func del(x, y int) int {return x - y
}func main() {x, y := 1,2fmt.Println(test(x,y,add))fmt.Println(test(x,y,del))
}

递归函数(函数自己调用自己)

// 递归函数,函数自己调用自己,超过一定的设定值return
func addMe(a, b int) int {a = a + bif a >= 10 {fmt.Printf("over %v", a)return a}fmt.Println("go on")return addMe(a, b)
}
func main() {addMe(1, 2)
}

struct 结构体类型

struct是一种数据类型,自己打造的用做一组 属性/字段 的容器。

方法与函数的区别:函数不属于任何类型,方法属于特定的结构体类型

func (接收者变量 接收者类型) 方法名(参数列表)(返回参数){函数体
}
下面有写例子
//常用:定义struct
type person struct {name  stringage   inthobby []string
}
//继承/结构体嵌套
type student struct {personweight string
}//自定义默认值
func Newperson(time string) person {a := person{Rating: 1, // fmt.Printf("%+v\n", Newperson()) 初始化不传参数输出零值,可以通过此方法设置默认值}return a
}func main() {// 初始化1var p personp.name = "zs"p.age = 10// 初始化2,若没有显示指定key,则需要把所有value都写完全p1 := person{"ls",20,[]string{"吃饭", "睡觉"},}// 初始化3p2 := person{name: "ww",age:  30,}fmt.Println(p, p1, p2) // {zs 10 []} {ls 20 [吃饭 睡觉]} {ww 30 []}// 初始化嵌套结构体m := student{person{name:"zzd",age:18}, "80kg"}// 访问字段fmt.Println(m.name, m.age, m.weight)// 修改字段m.weight = "99kg"fmt.Println(m.weight)
}
// 人结构体
type person struct {name stringage  int
}// 地址结构体
type address struct {province stringcity     stringperson   person // 结构体嵌套
}/* 结构体嵌套使用
addr := address{province: "bj",city:     "bj",person: person{name: "zzd",age:  20,},
}
fmt.Printf("%#v\n", addr) // main.address{province:"bj", city:"bj", person:main.person{name:"zzd", age:20}}
*/// 构造函数(构造结构体变量的函数):用于处理结构体返回值(在多处使用时建议使用构造函数)
func newPerson(name string, age int) *person {return &person{name: name,age:  age,}
}/*
方法与函数的区别:函数不属于任何类型,方法属于特定的结构体类型
func (接收者变量 接收者类型) 方法名(参数列表)(返回参数){函数体
}
*/
// person的方法Dream只有person可以调用
func (p person) Dream() {fmt.Printf("%s有梦想\n", p.name)
}
// 指针接收者
// 1. 需要修改结构体变量值时使用
// 2. 结构体本身比较大,拷贝开销大时使用
// 3. 保持一致性:如果一个方法使用了指针接收者,其它方法也统一使用
func (p *person) SetAge(newAge int) {p.age = newAge
}// josn类型结构体,字段首字母需大写,可通过`json:name`打tag转小写,注意``内不能有空格
type people struct {Name string `json:"name"`Age  int    `json:"age`
}// animal 结构体继承
type animal struct {name string
}// animal 实现了move函数
func (a *animal) move() {fmt.Printf("%s会动\n", a.name)
}// dog 结构体继承 animal
type dog struct {feet    int8*animal // 通过嵌套匿名结构体实现继承
}// dog 实现了 wang 函数
func (d *dog) wang() {fmt.Printf("%s 汪汪汪\n", d.name)
}func main() {// 结构体初始化方法1:使用key valuep1 := person{name: "huaz",age:  18,}fmt.Println("p1:", p1) // p1: {huaz 18}// 初始化方法2:赋值var p2 personp2.name = "zzd"p2.age = 19fmt.Println("p2:", p2) // p2: {zzd 19}// 初始化方法3:使用值列表,顺序需一致var p3 = person{"dd",29,}fmt.Println("p3:", p3) // p3: {dd 29}// 指针kv初始化p4 := &person{name: "xiaozi",age:  10,}fmt.Printf("p4: %#v\n", p4) // p4: &main.person{name:"xiaozi", age:10}// 字段无初始值默认为零值p5 := &person{name: "haha",}fmt.Printf("p5: %#v\n", p5) // p5: &main.person{name:"haha", age:0}// 空结构体不占空间var v struct{}fmt.Println(unsafe.Sizeof(v)) // 0// 1.序列化:结构体变量 ---> json格式的字符串// 2.反序列化:json格式 ---> 结构体变量o1 := people{Name: "dalin",Age:  3,}// 序列化jsonb, err := json.Marshal(o1)if err != nil {fmt.Printf("err: %v", err)return}fmt.Printf("json: %v\n", string(b)) // json: {"name":"dalin","Age":3}// 反序列化jsonstr := `{"name": "zz", "age": 10}`var o2 peoplejson.Unmarshal([]byte(str), &o2)fmt.Printf("ajson: %#v\n", o2) // ajson: main.people{Name:"zz", Age:10}// 调用构造函数p9 := newPerson("zs", 20)fmt.Printf("%#v\n", p9) // &main.person{name:"zs", age:20}// 调用方法p9.Dream() // zs有梦想p9.SetAge(30)fmt.Println(p9.age)// 初始化继承自animal的dogd1 := &dog{feet: 4,animal: &animal{name: "huahua",},}fmt.Println(d1.name, d1.feet) // huahua 4d1.move()                     // huahua会动d1.wang()                     // huahua 汪汪汪
}

interface 接口类型

接口是方法的集合,是一种特殊的类型,它规定了变量有哪些方法。只要实现该接口的方法,都可以用这个接口接收。可以理解为定义了一种规范?必须实现接口定义的方法,才能使用。go提倡面向接口编程。
场景:不关心一个变量是什么类型,只关心能调用它什么方法(注:参数规则须一致)。如:多种数据库引擎的增删改查。

接口定义

type 名字 interface{func1(arg1, arg2 ...)(return1, return2 ...)func2(arg1, arg2 ...)(return1, return2 ...)...
}

接口案例

package mainimport "fmt"
// 接口嵌套示例
// type animal interface {//  mover
//  eater
// }// type mover interface {//  move()
// }
// type eater interface {//  eat(s string)
// }// 定义一个car接口类型,只要实现了run()和color()都能被car调用,color()需要传一个参数
type car interface {run()color(s string)
}// drive方法调用run()
func drive(c car) {c.run()
}// bmw 和 aodi都实现了run和color,就可以被car接口直接调用
type bmw struct {name stringfeet int8
}// 使用值接收者实现接口和指针接收者实现接口的区别:
// 1.使用值接收者实现接口所有方法,结构体类型和结构体指针类型变量都能存
func (b bmw) run() {fmt.Printf("%s速度70m\n", b.name)
}func (b bmw) color(s string) {fmt.Printf("%s色\n", s)
}// 2.使用指针接收者实现接口所有方法,只能存结构体指针类型变量
// func (b *bmw) run() {//  fmt.Printf("%s速度70m\n", b.name)
// }// func (b *bmw) color(s string) {//  fmt.Printf("%s色\n", s)
// }
type aodi struct {name string
}func (a aodi) run() {fmt.Printf("%s速度80m\n", a.name)
}
func (a aodi) color(s string) {fmt.Printf("%s色\n", s)
}func main() {var c1 carfmt.Println(c1) // <nil>// 使用值接收者调用var b = bmw{name: "宝马",feet: 4,}// 使用指针接收者调用// var b = &bmw{//     name: "宝马",//     feet: 4,// }var a = aodi{name: "奥迪",}b.color("红") //红色a.color("黄") //黄色c1 = afmt.Println(c1) // {奥迪}c1 = bfmt.Println(c1) // {宝马}drive(a)        //奥迪速度80mdrive(b)        //宝马速度70m
}

package包

1.首字母大写外部可见,小写只能在当前包内用,如

package pkg2import "fmt"// 包变量可见性var a = 111 // 首字母小写,外部包不可见,只能在当前包内使用// 首字母大写外部包可见,可在其他包中使用
const Mode = 1type person struct { // 首字母小写,外部包不可见,只能在当前包内使用name string
}type Student struct {Name  string //可在包外访问的方法class string //仅限包内访问的字段
}type Payer interface {init() //仅限包内访问的方法Pay()  //可在包外访问的方法
}// 首字母大写,外部包可见,可在其他包中使用
func Add(x, y int) int {return x + y
}func age() { // 首字母小写,外部包不可见,只能在当前包内使用var Age = 18 // 函数局部变量,外部包不可见,只能在当前函数内使用fmt.Println(Age)
}// init方法只要调用包就会调用,无论是否引用
func init() {fmt.Println("this is init...")
}

2.包import导入的几种类型及ini()自动初始化调用

package mainimport ("fmt"     // 标准倒入包. "fmt"   // 重命名包名写法1,可直接调用Println("haha"),不需要写fmt了,即将该文件下内容都导入进来_ "os"    // 导入匿名包,只导入包,包括init()方法,不导入包内普通方法。例如数据库连接包等p2 "pkg2" // 重命名包名写法2
)func main() {a := p2.Mode // 可调用大写字母开头的变量fmt.Println(a)b := p2.Add(1, 4) // 可调用大写字母开头的方法fmt.Println(b)    // 标准包输出Println("haha")   // . "fmt" 包输出
}
=======输出=======
this is init...
1
5
haha

Goroutine并发编程

goroutine与线程

  • 一个操作系统线程对应用户态多个goroutine。
  • go程序可以同时使用多个操作系统线程。
  • goroutine和OS线程是多对多的关系,即m:n。

可增长的栈内存

  • OS线程(操作系统线程)一般都有固定的栈内存(通常为2MB),一个goroutine的栈在其生命周期开始时只有很小的栈(典型情况下2KB),goroutine的栈可以按需增大和缩小。

goroutine调度

  • GPM是Go语言运行时(runtime)层面的实现,是go语言自己实现的一套调度系统。区别于操作系统调度OS线程。
  • G就是goroutine,里面除了存放本goroutine信息外还有与所在P的绑定等信息。
  • P管理着一组goroutine队列,P里面会存储当前goroutine运行的上下文环境(函数指针,堆栈地址及地址边界),P会对自己管理的goroutine队列做一些调度(比如把占用CPU时间较长的goroutine暂停、运行后续的goroutine等等)当自己的队列消费完了就去全局队列里取,如果全局队列里也消费完了会去其他P的队列里抢任务。
  • M(machine)是Go运行时(runtime)对操作系统内核线程的虚拟, M与内核线程一般是一一映射的关系, 一个groutine最终是要放到M上执行。

通过sync.WaitGroup主动监听goroutine并发执行状态

// 通过sync.WaitGroup主动监听goroutine执行结束
var wg sync.WaitGroupfunc wgTest(i int) {time.Sleep(time.Second * time.Duration(rand.Intn(1)))fmt.Println(i)defer wg.Done() // wg执行完
}func a() {defer wg.Done() // wg执行完毕for i := 1; i < 10; i++ {fmt.Println("A:", i)}
}func b() {defer wg.Done() // wg执行完毕for i := 1; i < 10; i++ {fmt.Println("B:", i)}
}func main() {runtime.GOMAXPROCS(12) // 指定并发占用的cpu核数for i := 0; i < 10; i++ {wg.Add(1) // 启动一个goroutine wg计数器+1go wgTest(i)}wg.Add(2) // wg计数器+2go a()go b()wg.Wait() // wg等待上面流程执行
}
/*
并发结果输出通常都是乱序的
*/

通过channel实现Goroutine之间通信完成一组任务

  • 如果说Goroutine是一种支持并发编程的方式,那么通道(chan)就是一种与Goroutine通信的方式。通道(chan)让数据能够进入和离开Goroutine,可方便Goroutine之间进行通信。channel是先进先出的。
    不要通过共享内存来通信,而通过通信来共享内存
创建通道:c := make(chan (string))
向通道发送消息:c <- "sleep finish."
msg变量接收消息:msg := <-c
package mainimport ("errors""fmt""time"
)// 3.slowfunc将通道作为参数
func slowfunc(c chan string) {time.Sleep(time.Second * 2)c <- "sleep finish." // 4.函数执行完毕向通道c发送一条消息
}// 消息接收者遍历消息
func reciever(c chan string) {for msg := range c {fmt.Println(msg)}
}func pinger(c chan string) {t := time.NewTicker(1 * time.Second) // 新建NewTicker定时器// 向通道循环输入 pingfor {c <- "ping"<-t.C}
}// 指定通道访问权限
// 指定通道只读
func channelReader(messages <-chan string) {msg := <-messagesfmt.Println(msg)
}// 指定通道只写
func channelWriter(messages chan<- string) {messages <- "Hello world"
}// 指定通道读写
func channelReaderAndWriter(messages chan string) {msg := <-messagesfmt.Println(msg)
}// select验证通道case
func ping1(c chan string) {time.Sleep(time.Second * 4)c <- "ping1 finish"
}
func ping2(c chan string) {time.Sleep(time.Second * 3)c <- "ping2 finish"
}// select验证无限阻塞
func sender(c chan string) {t := time.NewTicker(1 * time.Second) // 定时器for {c <- "i am send a message" // 向通道c内发送消息<-t.C}
}
func main() {// errors包创建错误err := errors.New("some wrong")if err != nil {fmt.Println(err)}// fmt包格式化输出错误name, role := "zhidao", "zhang"errs := fmt.Errorf("the %v  %v quit", role, name)if err != nil {fmt.Println(errs)}// 如果说Goroutine是一种支持并发编程的方式,那么通道就是一种与Goroutine通信的方式。通道让数据能够进入和离开Goroutine,可方便Goroutine之间进行通信。// 不要通过共享内存来通信,而通过通信来共享内存// 一、通道实例c := make(chan (string)) // 1.创建通道go slowfunc(c)           // 2.go执行slowfunc函数,并将通道c传入msg := <-c               // 5.接收来自通道c的消息。阻塞直到收到消息为止,避免进程过早退出fmt.Println(msg)         // 6.接收成功打印消息// 二、缓冲通道:没有接收者的情况下使用message := make(chan (string), 2) // 1.创建长度为2的通道限制调度数量message <- "hello"message <- "world"close(message) // 2.发送完消息关闭通道fmt.Println("push two message onto chan")time.Sleep(time.Second * 1)reciever(message) // 3. 消息接收者// 三、通道和流程控制c1 := make(chan string) // 创建通道c1go pinger(c1)for i := 0; i < 2; i++ {msg1 := <-c1 // 接收m1通道消息fmt.Println(msg1)}// 四、使用select。select用于通道chan。跟switch用法很像,即:执行最先满足的条件chan1 := make(chan string)chan2 := make(chan string)go ping1(chan1)go ping2(chan2)select {case mm1 := <-chan1:fmt.Println("recieved ", mm1)case mm2 := <-chan2:fmt.Println("recieved", mm2)case <-time.After(500 * time.Millisecond): // 超时条件fmt.Println("time out")}// 五、使用select无限阻塞,随时能够返回消息,最终设置超时退出通道判断chan3 := make(chan string)stop := make(chan bool) // bool的零值为falsego sender(chan3)go func() {time.Sleep(time.Second * 2)fmt.Println("time is up!")stop <- true // 向stop通道内发送true}()for {select {case <-stop: // 接收stop通道内的条件,为ture则执行此处逻辑returncase msg := <-chan3: // 接收chan3通道的条件fmt.Println(msg)}}// ===========输出==============// some wrong// the zhang  zhidao quit// sleep finish.// push two message onto chan// hello// world// 例三:// ping// ping// 例四:// time out// 例五:// i am send a message// i am send a message// i am send a message// time is up!
}

小技巧

处理错误errors和fmt.Errorf

func main() {// errors包创建错误err := errors.New("some wrong")if err != nil {fmt.Println(err)}// fmt包格式化输出错误name, role := "zhidao", "zhang"errs := fmt.Errorf("the %v  %v quit", role, name)if err != nil {fmt.Println(errs)}
}

比较两个字符串相等

fmt.Println("sa" == "sa")  //true

比较两个slice相等(map、array类型)

dict := map[string]string{"A":"a","B":"b"}
dict1:=dict
fmt.Println(reflect.DeepEqual(dict,dict1))  //true

判断变量类型reflect.TypeOf(变量名)

Golang 基础学习相关推荐

  1. LearnGoProgramming-YouTube:Golang基础学习笔记

    Golang学习笔记 本文是从YouTube观看视频资料Golang初学者教程时顺手做的笔记,仅供辅助学习和回顾使用.由于水平有限,可能会存在一定的翻译错误和内容错误. YouTube资料来源: 文章 ...

  2. Golang基础学习总结

    1.不支持继承 重载 ,比如C++Java的接口,接口的修改会影响整个实现改接口的类行为的修改,Go 设计者认为这一特点或许根本没用. 2.必任何函数定义必须花括号跟在函数声明后面而不能换行 如 fu ...

  3. Golang基础学习1

    文章目录 package 基本管理单元 公共/私有 变量 Go 语言中的下划线 1. 下划线在 `import` 中 2. 下划线在代码中 Go 命令 package 基本管理单元 同一个packag ...

  4. Go语言学习笔记—golang基础语法

    视频来源:B站<golang入门到项目实战 [2022最新Go语言教程,没有废话,纯干货!]> 文章为自己整理的学习笔记,侵权即删,谢谢支持! 文章目录 golang基础语法 一.gola ...

  5. golang基础归纳

    1. hello-world package main import"fmt"func main(){ fmt.Println("Hello world, Go Go!& ...

  6. 我的Go+语言初体验——(4)零基础学习 Go+ 爬虫

    我的Go+语言初体验--(4)零基础学习 Go+ 爬虫 "我的Go+语言初体验" | 征文活动进行中- Go+ 语言非常适合编写爬虫程序,具有并发机制完善.并发数量大.占用资源少. ...

  7. 01_Go语言基础学习_Golang语言特性、环境搭建、第一个Go程序、包

    1. Golang语言特性: 垃圾回收: 1.内存自动回收,再也不需要开发人员管理内存: 2.开发人员专注业务实现,降低了心智负担 : 3.只需要new分配内存,不需要释放 天然并发: 1.从语言层面 ...

  8. 视频教程-桫哥-GOlang基础-01基本程序设计-Go语言

    桫哥-GOlang基础-01基本程序设计 多年互联网从业经验: 有丰富的的企业网站.手游.APP开发经验: 曾担任上海益盟软件技术股份有限公司项目经理及产品经理: 参与项目有益盟私募工厂.睿妙影音家庭 ...

  9. [Tour of Go] Golang基础

    Golang基础 看官网文档做的笔记.厌倦了每次捡起Go都要重看文档了. 官网的 Tutorials 大部分是一些使用Go进行开发的简单流程示例,我个人感觉是,按照按需自取的原则,稍微看一下,敲一下, ...

最新文章

  1. RDB 和 AOF 持久化的原理是什么?我应该用哪一个?它们的优缺点?
  2. android 打包hbuilder 高德地图加载不出来_十一黄金周地图很忙:百度获央视报道,高德忙道歉,究竟谁好用?...
  3. 005_logback介绍
  4. 5-1 Django的路由层(urlconf)
  5. 开发文件服务器,易语言开发文件服务器
  6. CUDA11.1安装教程(python3.8)
  7. Android图片编码机制深度解析(Bitmap,Skia,libJpeg)
  8. aspnet网站开发实例_给自己开发一个网站,这是我的方法。
  9. Python2 圆满落幕,Python 继续辉煌! | 原力计划
  10. Valgrind 使用简单说明-转
  11. 7月新的开始 - Axure学习05 - 元件库的创建
  12. U盘安装WIN10移动系统
  13. 关联性——组内相关系数
  14. 新唐N76E003单片机用APROM模拟EEPROM每次下载写入值复位为0XFF
  15. 回退到首页,返回浏览器窗口历史第一页 js
  16. Elasticsearch数据库all shards failed
  17. # 公有云?私有云?混合云?多云?行业云?傻傻分不清楚(下篇)
  18. 黑苹果系统安装常见问题汇集
  19. SRS4.0源码分析-main
  20. 如何在mac上播放iphone音频

热门文章

  1. linux低级格式化命令_低级Linux容器运行时的历史
  2. 每日IN语(2009-01-02)
  3. 联通宣布首批5G手机到位,究竟意味着什么?
  4. 基于安卓的google+ 分享
  5. mysql text oracle_深入理解Oracle Universal Installer (OUI) Text
  6. 模型如何压缩?使用轻量化的模型压缩技术剪枝(pruning)
  7. 审讯主机是计算机配件吗,益昌安大华审讯主机DH-HVR0404FE-S-H谁家价格好DH-HVR0404FE-S-H(大华)详细信息_产品参数_价格_联系方式_DAV数字音视工程网...
  8. 论文查重多少算合格?
  9. 两个Linux下的免费视频编辑软件
  10. 【图片批量处理软件分享】可批量添加水印/批量重命名/批量裁剪/批量缩小尺寸