Golang 快速入门

  • Golang 进阶
    • 反射
      • 变量内置 Pair 结构
      • reflect
      • 结构体标签
    • 并发知识
      • 基础知识
      • 早期调度器的处理
      • GMP 模型
      • 调度器的设计策略
    • 并发编程
      • goroutine
      • channel
      • 无缓冲的 channel
      • 有缓冲的 channel
      • 关闭 channel
      • channel 与 range
      • channel 与 select

Golang 快速入门:

  • 【Golang 快速入门】基础语法 + 面向对象
  • 【Golang 快速入门】高级语法:反射 + 并发
  • 【Golang 快速入门】项目实战:即时通信系统
  • 【Golang 快速入门】Go Modules + 生态拓展
  • 【Go 框架开发】Zinx 框架开发笔记

学习视频:8 小时转职 Golang 工程师,这门课很适合有一定开发经验的小伙伴,强推!

Golang 进阶

反射

变量内置 Pair 结构

var a string
// pair<statictype:string, value:"aceld">
a = "aceld"var allType interface{}
// pair<type:string, value:"aceld">
allType = astr, _ := allType.(string)

类型断言其实就是根据 pair 中的 type 获取到 value

// tty: pair<type: *os.File, value: "/dev/tty" 文件描述符>
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {fmt.Println("open file error", err)return
}// r: pair<type: , value: >
var r io.Reader
// r: pair<type: *os.File, value: "/dev/tty" 文件描述符>
r = tty// w: pair<type: , value: >
var w io.Writer
// w: pair<type: *os.File, value: "/dev/tty" 文件描述符>
w = r.(io.Writer) // 强转w.Write([]byte("HELLO THIS IS A TEST!!\n"))

仔细分析下面的代码:

  • 由于 pair 在传递过程中是不变的,所以不管 r 还是 w,pair 中的 tpye 始终是 Book
  • 又因为 Book 实现了 Reader、Wrtier 接口,所以 type 为 Book 可以调用 ReadBook() 和 WriteBook()
type Reader interface {ReadBook()
}type Writer interface {WriteBook()
}// 具体类型
type Book struct {}func (b *Book) ReadBook() {fmt.Println("Read a Book")
}func (b *Book) WriteBook() {fmt.Println("Write a Book")
}func main() {// b: pair<type: Book, value: book{} 地址>b := &Book{}// book ---> reader// r: pair<type: , value: >var r Reader// r: pair<type: Book, value: book{} 地址>r = br.ReadBook()// reader ---> writer// w: pair<type: , value: >var w Writer// w: pair<type: Book, value: book{} 地址>w = r.(Writer) // 此处的断言为什么成功?因为 w, r 的type是一致的w.WriteBook()
}

reflect

reflect 包中的两个重要方法:

// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
func ValueOf(i interface{}) Value {...}// ValueOf接口用于获取输入参数接口中的数据的值,如果接口为空则返回0
// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {...}// TypeOf用来动态获取输入参数接口中的值的类型,如果接口为空则返回nil

反射的应用:

  • 获取简单变量的类型和值:
func reflectNum(arg interface{}) {fmt.Println("type : ", reflect.TypeOf(arg))fmt.Println("value : ", reflect.ValueOf(arg))
}func main() {var num float64 = 1.2345reflectNum(num)
}
type :  float64
value :  1.2345
  • 获取结构体变量的字段方法:
type User struct {Id   intName stringAge  int
}func (u User) Call() {fmt.Println("user ius called..")fmt.Printf("%v\n", u)
}func main() {user := User{1, "AceId", 18}DoFieldAndMethod(user)
}func DoFieldAndMethod(input interface{}) {// 获取input的typeinputType := reflect.TypeOf(input)fmt.Println("inputType is :", inputType.Name())// 获取input的valueinputValue := reflect.ValueOf(input)fmt.Println("inputValue is :", inputValue)// 通过type获取里面的字段// 1.获取interface的reflect.Type,通过Type得到NumField,进行遍历// 2.得到每个field,数据类型// 3.通过field有一个Interface()方法,得到对应的valuefor i := 0; i < inputType.NumField(); i++ {field := inputType.Field(i)value := inputValue.Field(i).Interface()fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)}// 通过type获取里面的方法,调用for i := 0; i < inputType.NumMethod(); i++ {m := inputType.Method(i)fmt.Printf("%s: %v\n", m.Name, m.Type)}
}
inputType is : User
inputValue is : {1 AceId 18}
Id: int = 1
Name: string = AceId
Age: int = 18
Call: func(main.User)

结构体标签

结构体标签的定义:

type resume struct {Name string `info:"name" doc:"我的名字"`Sex  string `info:"sex"`
}func findTag(str interface{}) {t := reflect.TypeOf(str).Elem()for i := 0; i < t.NumField(); i++ {taginfo := t.Field(i).Tag.Get("info")tagdoc := t.Field(i).Tag.Get("doc")fmt.Println("info: ", taginfo, " doc: ", tagdoc)}
}func main() {var re resumefindTag(&re)
}
info:  name  doc:  我的名字
info:  sex  doc:

结构体标签的应用:JSON 编码与解码

import ("encoding/json""fmt"
)type Movie struct {Title  string   `json:"title"`Year   int      `json:"year"`Price  int      `json:"price"`Actors []string `json:"actors"`Test   string   `json:"-"` // 忽略该值,不解析
}func main() {movie := Movie{"喜剧之王", 2000, 10, []string{"xingye", "zhangbozhi"}, "hhh"}// 编码:结构体 -> jsonjsonStr, err := json.Marshal(movie)if err != nil {fmt.Println("json marshal error", err)return}fmt.Printf("jsonStr = %s\n", jsonStr)// 解码:jsonstr -> 结构体myMovie := Movie{}err = json.Unmarshal(jsonStr, &myMovie)if err != nil {fmt.Println("json unmarshal error", err)return}fmt.Printf("%v\n", myMovie)
}
jsonStr = {"title":"喜剧之王","year":2000,"price":10,"actors":["xingye","zhangbozhi"]}
{喜剧之王 2000 10 [xingye zhangbozhi] }

其他应用:orm 映射关系 …

并发知识

基础知识

早期的操作系统是单进程的,存在两个问题:

1、单一执行流程、计算机只能一个任务一个任务的处理

2、进程阻塞所带来的 CPU 浪费时间

多线程 / 多进程 解决了阻塞问题:

但是多线程又面临新的问题:上下文切换所耗费的开销很大

进程 / 线程的数量越多,切换成本就越大,也就越浪费。

有可能 CPU 使用率 100%,其中 60% 在执行程序,40% 在执行切换…

多线程 随着 同步竞争(如 锁、竞争资源冲突等),开发设计变的越来越复杂。

多线程存在 高消耗调度 CPU高内存占用 的问题:


如果将内核空间和用户空间的线程拆开,也就出现了协程(其实就是用户空间的线程)

内核空间的线程由 CPU 调度,协程是由开发者来进行调度。

用户线程,就是协程。内核线程,就是真的线程。

然后在内核线程与协程之间,再加入一个协程调度器:实现线程与协程的一对多模型

  • 弊端:如果一个协程阻塞,会影响下一个的调用(轮询的方式)

如果将上面的模型改成一对一的模型,虽然没有阻塞,但是和以前的线程模型没有区别了…

再继续优化成多对多的模型,则将主要精力放在优化协程调度器上:

内核空间是 CPU 地盘,我们无法进行太多优化。

不同的语言想要支持协程的操作,都是在用户空间优化其协程处理器。

Go 对协程的处理:

早期调度器的处理

老调度器有几个缺点:

  1. 创建、销毁、调度 G 都需要每个 M 获取锁,形成了激烈的锁竞争
  2. M 转移 G 会造成延迟和额外的系统负载
  3. 系统调用(CPU 在 M 之前的切换)导致频繁的线程阻塞和取消阻塞操作,增加了系统开销

GMP 模型

调度器的设计策略

调度器的 4 个设计策略:复用线程、利用并行、抢占、全局G队列


复用线程:work stealing、hand off

  • work stealing 机制:某个处理器的本地队列空余,从其他处理器中偷取协程来执行

    注意,这里是从某个处理器的本地队列偷取,还有从全局队列中偷取的做法

  • hand off 机制:如果某个线程阻塞,会将处理器资源让给其他线程。


利用并行:利用 GOMAXPROCS 限定 P 的个数 = CPU 核数 / 2


抢占


全局G队列:基于 warlk stealing 机制,如果所有处理器的本地队列都没有协程,则从全局获取。

并发编程

goroutine

创建 goroutine:

// 子routine
func newTask() {i := 0for {i++fmt.Printf("new Goroutie: i = %d\n", i)time.Sleep(1 * time.Second)}
}// 主routine
func main() {// 创建一个子进程 去执行newTask()流程go newTask()i := 0for {i++fmt.Printf("main goroutine: i = %d\n", i)time.Sleep(1 * time.Second)}
}
main goroutine: i = 1
new Goroutie: i = 1
new Goroutie: i = 2
main goroutine: i = 2
main goroutine: i = 3
new Goroutie: i = 3
...

退出当前的 goroutine 的方法 runtime.Goexit(),比较以下两段代码:

func main() {go func() {defer fmt.Println("A.defer")func() {defer fmt.Println("B.defer")fmt.Println("B")}()fmt.Println("A")}()// 防止程序退出for {time.Sleep(1 * time.Second)}
}
B
B.defer
A
A.defer

执行了退出 goroutine 的方法:

func main() {go func() {defer fmt.Println("A.defer")func() {defer fmt.Println("B.defer")runtime.Goexit() // 退出当前goroutinefmt.Println("B")}()fmt.Println("A")}()// 防止程序退出for {time.Sleep(1 * time.Second)}
}
B.defer
A.defer

channel

channel 用于在 goroutine 之间进行数据传递:

make(chan Type) // 等价于 make(chan Type, 0)
make(chan Type, capacity)
channel <- value      // 发送value到channel
<-channel                        // 接收并将其丢弃
x := <-channel          // 从channel中接收数据,并赋值给x
x, ok := <-channel  // 功能同上,同时检查通道是否已关闭或为空

channel 的使用:

func main() {// 定义一个channelc := make(chan int)go func() {defer fmt.Println("goroutine 结束")fmt.Println("goroutine 正在运行")c <- 666 // 将666发送给c}()num := <-c // 从c中接受数据, 并赋值给numfmt.Println("num = ", num)fmt.Println("main goroutine 结束...")
}
goroutine 正在运行...
goroutine结束
num =  666
main goroutine 结束...

上面的代码(使用 channel 交换数据),sub goroutine 一定会在 main goroutine 之后运行

  • 如果 main goroutine 运行的快,会进入等待,等待 sub goroutine 传递数据过来

  • 如果 sub goroutine 运行的快,也会进入等待,等待 main routine 运行到当前,然后再发送数据

无缓冲的 channel

  • 第 1 步,两个 goroutine 都到达通道,但哪个都没有开始执⾏发送或者接收。

  • 第 2 步,左侧的 goroutine 将它的⼿伸进了通道,这模拟了向通道发送数据的⾏为。

    这时,这个 goroutine 会在通道中被锁住,直到交换完成。

  • 第 3 步,右侧的 goroutine 将它的手放⼊通道,这模拟了从通道⾥接收数据。

    这个 goroutine ⼀样也会在通道中被锁住,直到交换完成。

  • 第 4 步和第 5 步,进⾏交换。

  • 第 6 步,两个 goroutine 都将它们的手从通道里拿出来,这模拟了被锁住的 goroutine 得到释放。

    两个 goroutine 现在都可以去做其他事情了。

有缓冲的 channel

  • 第 1 步,右侧的 goroutine 正在从通道接收一个值。

  • 第 2 步,右侧的这个 goroutine 独立完成了接收值的动作,左侧的 goroutine 正在发送一个新值到通道里。

  • 第 3 步,左侧的 goroutine 还在向通道发送新值,⽽右侧的 goroutine 正在从通道接收另外一个值。

    这个步骤⾥的两个操作既不是同步的,也不会互相阻塞。

  • 第 4 步,所有的发送和接收都完成,⽽通道里还有⼏个值,也有一些空间可以存更多的值。

特点:

  • 当 channel 已经满,再向⾥面写数据,就会阻塞。
  • 当 channel 为空,从⾥面取数据也会阻塞。
func main() {// 带有缓冲的channelc := make(chan int, 3)fmt.Println("len(c) = ", len(c), "cap(c) = ", cap(c))go func() {defer fmt.Println("子go程结束")for i := 0; i < 3; i++ {c <- ifmt.Println("子go程正在运行,发送的元素 =", i, "len(c) = ", len(c), " cap(c) = ", cap((c)))}}()time.Sleep(2 * time.Second)for i := 0; i < 3; i++ {num := <-c // 从c中接收数据,并赋值给numfmt.Println("num = ", num)}fmt.Println("main 结束")
}
len(c) =  0 cap(c) =  3
子go程正在运行,发送的元素 = 0 len(c) =  1  cap(c) =  3
子go程正在运行,发送的元素 = 1 len(c) =  2  cap(c) =  3
子go程正在运行,发送的元素 = 2 len(c) =  3  cap(c) =  3
子go程结束
num =  0
num =  1
num =  2
main 结束

上例中,可以尝试分别改变 2 个 for 的循环次数进行学习。

关闭 channel

func main() {c := make(chan int)go func() {for i := 0; i < 5; i++ {c <- i}// close可以关闭一个channelclose(c)}()for {// ok为true表示channel没有关闭,为false表示channel已经关闭if data, ok := <-c; ok {fmt.Println(data)} else {break}}fmt.Println("Main Finished..")
}
0
1
2
3
4
Main Finished..

channel 不像文件一样需要经常去关闭,只有当确实没有任何发送数据了,或者想显式的结束 range 循环之类的,才去关闭 channel,注意:

  • 关闭 channel 后,无法向 channel 再发送数据(引发 panic 错误后导致接收立即返回零值)
  • 关闭 channel 后,可以继续从 channel 接收数据
  • 对于 nil channel,⽆论收发都会被阻塞

channel 与 range

func main() {c := make(chan int)go func() {defer close(c)for i := 0; i < 5; i++ {c <- i}}()// 可以使用range来迭代不断操作channelfor data := range c {fmt.Println(data)}fmt.Println("Main Finished..")
}

channel 与 select

select 可以用来监控多路 channel 的状态:

func fibonacii(c, quit chan int) {x, y := 1, 1for {select {case c <- x:// 如果c可写,则进入该casex, y = y, x+ycase <-quit:// 如果quit可读,则进入该casefmt.Println("quit")return}}
}func main() {c := make(chan int)quit := make(chan int)// sub gogo func() {for i := 0; i < 6; i++ {fmt.Println(<-c)}quit <- 0}()// main gofibonacii(c, quit)
}
1
1
2
3
5
8
quit

【Golang 快速入门】高级语法:反射 + 并发相关推荐

  1. 【Golang 快速入门】项目实战:即时通信系统

    Golang 快速入门 即时通信系统 - 服务端 版本一:构建基础 Server 版本二:用户上线功能 版本三:用户消息广播机制 版本四:用户业务层封装 版本五:在线用户查询 版本六:修改用户名 版本 ...

  2. golang快速入门[8.3]-深入理解IEEE754浮点数

    前文 golang快速入门[1]-go语言导论 golang快速入门[2.1]-go语言开发环境配置-windows golang快速入门[2.2]-go语言开发环境配置-macOS golang快速 ...

  3. PHP快速入门-基础语法及面向对象

    配置sublime {"cmd": ["php", "$file"],"file_regex": "php$& ...

  4. Golang快速入门上手

    Golang 1.介绍 简介 ​ Go起源于 2007 年,并在 2009 年正式对外发布.Go 是非常年轻的一门语言,它的主要目标是"兼具 Python 等动态语言的开发速度和 C/C++ ...

  5. golang快速入门--语言基础

    语言基础语法 行分隔符 在golang中,多个语句写在同一行,必须使用分号 " ; " 分隔开 注释 单行注释 使用// 即可表示 多行注释 使用/-/ 表示 字符串连接 允许使用 ...

  6. Python快速入门--基本语法

    5.1 Python简介 本章将介绍Python的最基本语法,以及一些和深度学习还有计算机视觉最相关的基本使用. 5.1.1 Python简史 Python是一门解释型的高级编程语言,特点是简单明确. ...

  7. Vue_02 快速入门 基础语法1

    目录 1. 模板语法 1.1 插值 1.1.1 文本 1.1.2 html 1.1.3 属性 1.1.4 表达式 1.2 指令 1.2.1 核心指令 2. 过滤器 2.1 局部过滤器 2.2 全局过滤 ...

  8. HTML5快速入门基础语法

    文章目录 前言:一些需要注意的小细节 1.meta标签 2.块元素 3.行内元素 inline element 一.列表 二.HTML 链接 三.结构化语义标签(布局标签) 四.图片标签 五.内联框架 ...

  9. python语法速成方法_一天快速入门Python语法基础之函数

    #一.定义函数 defHello():print("hello") Hello()#调用函数 #1.实参和形参 def Hello(name): #name是形参 print(&q ...

最新文章

  1. 服务发现对比:Zookeeper vs. etcd vs. Consul
  2. wider face data 在 faster rcnn 上的实践记录(caffe)
  3. 技术解密|阿里云多媒体 AI 团队是凭借什么拿下 CVPR2021 5冠1亚的?
  4. Json串和java对象进行转时
  5. tcp wireshark 过滤syn_使用 WireShark 分析 TCP/IP 三次握手 和 四次挥手
  6. Redis概述_使用命令对redis的数据进行增删改查_Jedis连接redis进行数据操作_redis进行数据缓存案例
  7. php checkbox 保存,PHP中在数据库中保存Checkbox数据
  8. C#实现DataTable按天分组并计数
  9. php大写数字转换,PHP把数字转成人民币大写的函数分享
  10. Asp.net网站的ClickOnce自动部署(3)-虚拟目录的配置
  11. 关于翻译书籍版权的讨论
  12. python cpk计算器_Python进行CPK计算
  13. 在xp IIS上搭建动易论坛注意事项
  14. 利用css做导航栏,利用CSS制作导航栏
  15. 概率图模型和马尔可夫模型
  16. csdn发文——Markdown编辑器
  17. UG NX 12 草图曲线
  18. LINUX | 如何开放vultr的端口
  19. Java岗大厂面试百日冲刺 - 日积月累,每日三题【Day23】—— 算法1
  20. User Story用户情景与用例规约

热门文章

  1. 一篇文章,可以同时拥有4份收益,你知道吗?
  2. Linux平台安装Clion
  3. powerbi发布本地_PowerBI中本地数据的注意事项
  4. sql server 监视_使用SQL Server Reporting Services进行快速,肮脏的服务器监视
  5. 如何将SQL Server 2017主数据服务模型迁移到另一台服务器
  6. [Swift]LeetCode480. 滑动窗口中位数 | Sliding Window Median
  7. Python3 找不到库
  8. Camera2点击对焦实现
  9. 将Visual Studio打造成为Node.js IDE
  10. 新议题,好多年前的华为面试题