【Golang 快速入门】高级语法:反射 + 并发
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 对协程的处理:
早期调度器的处理
老调度器有几个缺点:
- 创建、销毁、调度 G 都需要每个 M 获取锁,形成了激烈的锁竞争。
- M 转移 G 会造成延迟和额外的系统负载。
- 系统调用(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 快速入门】高级语法:反射 + 并发相关推荐
- 【Golang 快速入门】项目实战:即时通信系统
Golang 快速入门 即时通信系统 - 服务端 版本一:构建基础 Server 版本二:用户上线功能 版本三:用户消息广播机制 版本四:用户业务层封装 版本五:在线用户查询 版本六:修改用户名 版本 ...
- golang快速入门[8.3]-深入理解IEEE754浮点数
前文 golang快速入门[1]-go语言导论 golang快速入门[2.1]-go语言开发环境配置-windows golang快速入门[2.2]-go语言开发环境配置-macOS golang快速 ...
- PHP快速入门-基础语法及面向对象
配置sublime {"cmd": ["php", "$file"],"file_regex": "php$& ...
- Golang快速入门上手
Golang 1.介绍 简介 Go起源于 2007 年,并在 2009 年正式对外发布.Go 是非常年轻的一门语言,它的主要目标是"兼具 Python 等动态语言的开发速度和 C/C++ ...
- golang快速入门--语言基础
语言基础语法 行分隔符 在golang中,多个语句写在同一行,必须使用分号 " ; " 分隔开 注释 单行注释 使用// 即可表示 多行注释 使用/-/ 表示 字符串连接 允许使用 ...
- Python快速入门--基本语法
5.1 Python简介 本章将介绍Python的最基本语法,以及一些和深度学习还有计算机视觉最相关的基本使用. 5.1.1 Python简史 Python是一门解释型的高级编程语言,特点是简单明确. ...
- 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 全局过滤 ...
- HTML5快速入门基础语法
文章目录 前言:一些需要注意的小细节 1.meta标签 2.块元素 3.行内元素 inline element 一.列表 二.HTML 链接 三.结构化语义标签(布局标签) 四.图片标签 五.内联框架 ...
- python语法速成方法_一天快速入门Python语法基础之函数
#一.定义函数 defHello():print("hello") Hello()#调用函数 #1.实参和形参 def Hello(name): #name是形参 print(&q ...
最新文章
- Facebook 的AI翻身之战!
- 提取ESX/ESXI4.0脚本安装文件ks.cfg、ks-first.cfg和ks-first-safe.cfg
- php 如何做ftp传输,php如何实现ftp上传
- 朱林北京大学计算机学院,北大、清华状元谈英语学习经验-20210411145045.docx-原创力文档...
- [jzoj NOIP2018模拟 11.01]
- java密钥库文件存在但为空_java安全套接层SSL示例
- Delphi编译指令说明
- python 实现统计ftp服务器指定目录下文件夹数目、文件数目及所有文件大小 本次主要为满足应用方核对上传到ftp服务器的文件是否缺漏。 主要要求:指定目录下,文件夹数目/文件数目/所有文件大小
- php100教程源码,PHP100 视频教程 2012-2013版_PHP教程
- php实现一个简单的购物网站
- 天龙八部手游有网络显示网络或服务器异常,天龙八部手游微信登录不了_微信登录异常解决办法_玩游戏网...
- Algorithm:字典序最小问题
- TrueCrypt的原理
- 云计算机技术的运用,三分钟为你详细解析云计算技术与应用
- 宝塔同时安装苹果cms海洋cms_海洋cms新手入门安装配置教程
- ASRT中文语音识别系统
- matlab求函数的极限
- form-data和x-www-form-urlencoded的区别和延伸
- Linux下MongoDB的入门安装、配置与启动
- 谁来买我们的DRAM?美光公司摸摸干瘪的口袋
热门文章
- 什么行业,只要付出辛苦就稳赚不赔,每天稳定纯利润300就知足?
- 你根本就不需要认识这么多大佬
- 分享一个四两拨千斤的真实故事
- 为什么还有那么多人用SVN?
- Html 点透镂空遮罩,swift 实现遮罩部分区域“挖洞”效果和点击事件穿透
- 学习SQL:使用日期和时间函数创建SQL Server报表
- Scrapy框架的介绍和基本使用
- 006-Zabbix agent on Zabbix server is unreachable for 5 minutes
- VS 2005 2008 项目模板丢失问题
- Chemical table CFR500 div2D(并查集)