Go学习[合集]
文章目录
- Go学习-Day1
- Go学习-Day2
- 标识符
- 变量基础语法
- 字符串类型
- 类型转换
- string和其他基本类型转换
- 其他类型转string
- string转其他类型
- 指针类型
- 运算符
- 标准IO
- 分支语句
- Go学习-Day3
- 循环语句
- 函数
- 声明
- init函数
- 匿名函数
- 闭包
- defer
- Go学习-Day4
- 函数
- 值传递,引用传递
- 常用的函数
- 异常处理
- 数组
- Slice切片
- Go学习-Day5
- map
- 增加和更新
- 删除
- 查询
- 遍历(for-range)
- map切片
- 关于哈希表遍历的一点看法
- 对map的key排序
- 结构体与OOP
- 声明、初始化、序列化
- 方法
- 工厂模式
- Go学习-Day6
- 封装
- 继承
- 接口
- Go学习-Day7
- 断言
- 文件
- 打开/关闭文件
- 读取文件
- 写入文件
- 命令行参数解析
- Args
- flag包
- JSON
- Go学习-Day8
- 单元测试
- Goroutine
- 进程和线程
- 并发和并行
- Go协程和主线程
- MPG模式
- CPU相关
- 协程并行的资源竞争
- Go学习-Day9
- Channel
- 声明
- 存入取出
- 一个简单的死锁分析
- Go学习-Day10
- 反射
- 网络编程
- 监听端口小Demo
- 客户端
- 发送&&接收
Go学习-Day1
-
个人博客:CSDN博客
-
打卡。
-
Go语言的核心开发团队:
- Ken Thompson (C语言,B语言,Unix的发明者,牛人)
- Rob Pike(UTF-8发明人)
- Robert Griesemer(协助HotSpot编译器,Js引擎V8)
-
Go语言有静态语言的安全和性能和动态语言开发维护的效率。
-
Go语言特性
- 继承了C语言很多概念(Ken爷!)包括指针。
- 引入包的概念
- 垃圾回收机制
- 天然并发(核心)
- 管道通信机制(Channel)
- 函数可以有多返回值
- 新增切片slice,延时执行defer
-
Hello World (一定要注意目录结构!)
-
通过go build来编译go文件,得到exe文件
-
关于文件夹架构,一定要准确,不然找不到包。
-
%GOPATH%
-
src
-
go_code
-
project00 //项目名open这个项目
-
project01
- main//包
- pkg//其他包
-
-
-
-
注意配置PATH,GOPATH(项目的位置),GOROOT(SDK的位置)
-
并且配置一些settings里面相应的变量
- 琐碎的细节
- go语言没有分号结尾,因此一行就写一条语句
- 定义的变量和导入的包如果没有用到就无法通过编译
- 块注释不能嵌套,尽量使用行注释
Go学习-Day2
标识符
- 驼峰法,首字母大写可以在其他包里使用,首字母小写只能在本包内使用
- 跨包使用,的import地址从src的子目录开始,src以及src所在的GOPATH自动补全
变量基础语法
-
定义变量
-
var i int = 10
-
var关键字+变量名+变量类型
-
var i = 10
-
自动推断类型
-
i := 10
-
简略写法
-
对应的,可以声明多个变量
-
var a, b, c int = 1, "a", 2
-
var a, str1, b = 1, "a", 2
-
a, str1, b := 1, "a", 2
-
var (i = 1j = 2 )
-
另一种声明方法,开发中常用
-
import ("fmt""unsafe" )
-
导包也可以类似这样
- 不能改变变量的类型,例如开始赋值整数,后来又赋值浮点数。
- 默认值,数默认为0,字符串默认为空串
字符串类型
-
利用UTF-8编码,支持中文
-
go中字符串是常量,无法修改
-
引号
- 双引号"" :会识别转义字符
- 反引号``:不识别转义字符(防止SQL注入之类的?)
-
加号拼接,可以分行写(加号放行尾)
类型转换
-
go不会自动转换类型,需要显式转换
-
var i int = 1 var j float32 = float32(i)
string和其他基本类型转换
其他类型转string
func main() {var a int = 10var b float32 = 3.14var s string = fmt.Sprintf("%d %.2f", a, b)fmt.Println(s)
}
string转其他类型
b, err := strconv.ParseBool("true")
f, err := strconv.ParseFloat("3.1415", 64)//返回64位要 强转
i, err := strconv.ParseInt("-42", 10, 64)//进制 和 位数
u, err := strconv.ParseUint("42", 10, 64)
- 返回值有两个
- 可以使用_代替err,下划线是特殊的变量,表示忽略返回值。
- 如果无法转换则返回0
- 学英语:parse是分析的意思,strconv = string-conversion
指针类型
- 和C语言类似,不赘述。
运算符
- 没有三元运算符,只能用if else,if后面没有小括号
- 运算与C语言一致
- 自增自减只能单独使用,不能在自增自减的同时给变量赋值
- 自增自减的++和–都必须放在变量的后边!
标准IO
-
string也是基本类型,传入&地址。
-
func main() {var str string_, _ = fmt.Scanln(str)fmt.Println(str) }
分支语句
- 基本和C语言一致
- switch 不用break;
- 可以匹配多个表达式,逻辑或的关系
- case,switch后面是一个表达式(不一定是常量)
- case和switch的数据类型必须一致
- case的常量(字面量)不能重复
- switch后面可以不带表达式,可以代替if else作分支选择
- fallthrough关键字可以穿透到下一分支,用来代偿省略break的功能
Go学习-Day3
- 个人博客:CSDN博客
循环语句
-
传统方法
-
func main() {for i := 1; i < 10; i++ {fmt.Println("hello!")} }
-
for - range方法
-
func main() {str := "abcde"for idx, val := range str {fmt.Printf("%v %c\n", idx, val)} }
-
idx是下标,val是值
-
go没有while和do-while使用for来实现
-
for {if i > 10 {break}fmt.Println(i)i++}
函数
声明
-
func 函数名(形参列表)返回值列表 {}
-
func add(a int, b int) int {return a + b }
-
分包写函数
-
package mainimport ("fmt""go_code/project01/model" )func main() {fmt.Println(model.Add(1, 2)) }
-
包名和文件夹名可以不一致,这样下面调用的时候也要用另外的包名,一般来说,我们习惯于名字保持一致,这样我们导入包的时候,就就能知道包名是什么了。
-
再复习一个点,Go语言没有public private关键字,是用变量和函数第一个字母大小写来判断公有还是私有,大写是公有,小写是私有。
-
导包的路径,是从GOPATH/src 后面的部分,一直导到包文件夹。
-
在同一包下,不能又相同的函数名,不支持函数的重载
-
如果要编译可执行文件必须声明main包,main包是唯一的。
init函数
- 每个源文件都可以包含一个init函数,这个函数会在main函数之前被调用,全局变量定义,init函数,main函数的顺序调用
- 可能相当于类当中的构造函数
匿名函数
-
相当于把整个函数体当作函数的名字,后面的括号就是传入的参数列表
-
func main() {res := func(a int, b int) int {return a + b}(1, 2)fmt.Println(res) }
-
如果不带括号,可以重复调用匿名函数,类似lamda表达式
-
res := func(a int, b int) int {return a + b}//fmt.Println(res)fmt.Println(res(1, 2))
闭包
-
func main() {f := func() func(int) int {var n int = 10return func(x int) int {n = x + nreturn n}}ff := f()ff(1)ff(2)fmt.Println(ff(3)) }
-
答案16
-
这里是一个返回值是(int)int的匿名函数,返回了一个含有未知参数并且引用了n的匿名函数,对这个匿名函数多次调用。
-
函数和引用的外部变量构成了闭包,相当于一个类,第一次调用得到一个匿名函数,可以类比成一个构造方法,构造出了一个类,n是类的一个成员。
-
或者,我们这样想,这个匿名函数和他所引用的变量构成的闭包,在匿名函数第一次返回的时候,这些变量也在相同的作用域进行声明。
defer
-
to delay sth until a later time 推迟;延缓;展期(摘自牛津)
-
func main() {defer fmt.Println("ok1")defer fmt.Println("ok2")fmt.Println("ok3")fmt.Println("ok4")}
-
输出顺序是:3->4->2->1
-
defer先不执行,等到函数快要释放的时候,defer执行顺序遵从栈的顺序,先进后出
-
当语句压入栈的时候,相关引用的变量也会拷贝一份进入栈。
Go学习-Day4
- 个人博客:CSDN博客
函数
值传递,引用传递
- 值传递直接拷贝值,一般是基本数据类型,数组,结构体也是
- 引用传递传递地址 ,效率高,指针,slice切片,map,管道,interface等
常用的函数
-
len(string str)//求字符串长度,自带的不用包,中文一个字三字节
-
转成[]rune来处理
-
[]byte转string
-
str = string([]byte{...})
-
查找子串是否存在
-
若干字符函数
-
strings.Contains("aaa", "aaa") //bool strings.Index("aaa", "aaa")//返回下标 strings.LastIndex("aaa", "aaa")//返回最后一个下标,没有就返回-1 strings.Replace(str, str1, str2, n)//把1中str1替换成str2,n是替换个数,-1表示全部替换 strings.Split(str, "某字符")//分割字符串 strings.TrimSpace(str)//裁剪空格,去掉前导和后导空格 strings.Trim(str, "字符集")//去掉指定字符 strings.TrimLeft()//同上,去掉左侧,并且还有TrimRight strings.HasPrefix(str, "后缀")//前缀匹配 strings.HasSuffix()//同上,但是后缀
-
若干时间函数
-
now := time.Now()//返回时间类型,当前时间 //2023-08-23 16:37:07.5402748 +0800 CST m=+0.001148901 大概是这样 //时间类型是结构体,可以使用.运算符来获取其他时间信息,now.Year() //月份可以直接转int time.Sleep(time.Millisecond * 100)//只能用乘法,不能有浮点数,利用时间单位常量 time.Unix()//获取unix秒时间戳 time.UnixNano()//unix纳秒时间戳
-
内置函数built-in
-
len()//统计字符串长度,数组大小 new(Type) *Type //参数为类型,返回一块对应大小的清空的内存块的指针
异常处理
-
Go中没有try catch
-
Go利用defer panic recover来处理异常
-
抛出一个panic的异常,在defer中通过recover捕获异常
-
package mainimport "fmt"func test() {defer func() {err := recover() //捕获异常if err != nil {fmt.Println(err)}}()num1 := 10num2 := 0res := num1 / num2fmt.Println(res) } func main() {test()fmt.Println("ok") }
-
通过捕获异常,可以使得程序不崩溃停止!main函数的其他部分照常运行
-
自定义错误
-
func myError(x int) (err error) {if x == 0 {return nil} else {return errors.New("错误")} }func test() {err := myError(1)if err != nil {panic(err)} }func main() {test() }
-
panic会终止程序
-
捕获自定义错误
-
func myError(x int) (err error) {if x == 0 {return nil} else {return errors.New("错误")} }func test() {defer func() {err := recover() //捕获异常if err != nil {fmt.Println(err)}}()err := myError(1)if err != nil {panic(err)} }func main() {test()fmt.Println("ok") }
数组
-
定义
-
func main() {var arr [10]intarr[0] = 1fmt.Println(arr)}
-
数组名地址&arr
-
初始化
-
var arr [3]int = [3]int{1, 2, 3}var arr = [3]int{1, 2, 3}var arr = [...]int{1, 2, 3}var arr = [...]int{1: 800, 0: 900, 2: 999}//指定下标var arr := [...]int{1, 2, 3} //自动推导
-
遍历for-range 同string 不赘述
-
数组中的元素可以是任何合法的类型,但是不能混用
-
Go中数组是值类型,会进行拷贝,要想修改原数组,需要使用指针,写法类似C语言的行指针
Slice切片
-
切片是引用类型,传递地址
-
切片和数组类似,但是长度是可以变化的!
-
声明
-
var a []int
-
func main() {var arr [5]int = [...]int{1, 2, 3, 4, 5}slice := arr[1:3] //从下标1与下标3,左闭右开的区间fmt.Println(slice) }
-
通过make声明
-
func main() {slice := make([]int, 2, 4)//容量可以不声明fmt.Println(slice) }
-
make在底层维护一个数组,这个数组对外不可见
-
直接声明
-
var slive []string = []string
-
遍历和数组类似,不再赘述
-
简写
-
var slice = arr[:end] // var slice = arr[0:end]前缀,不含end var slice = arr[start:]//var slice = arr[start:]后缀 var slice = arr[:]//var slice = arr[0:len(arr)]全长
-
切片可以继续切片
-
切片可以追加,可以追加多个数,可以追加多个切片,利用append将追加后的切片赋值给原来的切片
-
slice1 = append(slice1, 1, 2, 3)//追加数 slice1 = append(slice1, slice1...)//要三个点
-
Go底层会创建一个新的数组,然后切片这个新的数组,这些过程均不可见
-
string可以进行切片处理
-
str := "sssssss" slice := str[2:]//从下标2开始切后缀
-
string底层也指向一个byte数组,我们用切片来拷贝这个只读的byte数组再进行操作
-
通过切片能够改变字符串
-
arr := byte[](str) arr[0] = 'a' str = string(arr) //但是不支持中文 arr := rune[](str) arr[0] = '好' str = string(arr) //弄中文 func main() {str := "?????"arr := []rune(str)arr[0] = '好'fmt.Println(string(arr)) }
Go学习-Day5
- 个人博客:CSDN博客
map
-
map是一个key-value的数据结构,又称为字段或关联数组
-
Golang自带的map是哈希表
-
声明
-
import "fmt"func main() {var a map[int]intfmt.Println(a) }
-
slice,map和func不能作为键值
-
声明map是不会分配内存的,初始化需要用make
-
import "fmt"func main() {var a map[int]inta = make(map[int]int, 3)//可以存放三个键值对fmt.Println(a) }
-
Go的map的键值是没有顺序的
-
自动增长
-
func main() {a := make(map[int]int)a[1] = 2a[2] = 1fmt.Println(a) }
-
直接初始化
-
func main() {a := map[int]int{1: 1,2: 2,//这里也要,}fmt.Println(a) }
增加和更新
- 直接给键值赋值即可
删除
-
delete(map, 1)//删除map键值为1的对,如果不存在不会操作
-
如果要完全清空
-
遍历key来删除,或者让map赋值一个新的map,给GC回收
查询
val, flag := mp[1] //flag是bool,找到是true,没找到是false,val是对应值
遍历(for-range)
-
func main() {a := map[int]int{1: 1,2: 2,}for k, v := range a {fmt.Println(k, v)}fmt.Println(a) }
map切片
- 同样slice的用法,注意map也要make就行,相当于在底层维护一个map类型的数组
关于哈希表遍历的一点看法
- 大概是这样的,在底层有一个迭代器链表,或者,有一个指针数组,每次存放一个指针,迭代器依次访问这些指针,但是在添加元素的时候, 会rehash,导致顺序变化。所以Go的设计者把他设置成无序,每次都打乱这个数组,防止程序员误用哈希map
对map的key排序
-
将键值追加到切片内,然后对切片排序
-
import ("fmt""sort" )func main() {mp := make(map[int]int, 10)mp[1] = 2mp[3] = 1mp[2] = 5mp[5] = 6var keys []int //切片for key, _ := range mp {keys = append(keys, key)}sort.Ints(keys)fmt.Println(keys) }
-
map是引用类型
结构体与OOP
声明、初始化、序列化
-
go语言是用struct来面向对象的
-
声明
-
type Node struct {X intY int }func main() {var a Node//空间自动分配fmt.Println(a)} //输出{0 0}
-
赋值
-
func main() {var a Nodea.X = 1a.Y = 2fmt.Println(a)} //输出{1 2}
-
初始化
-
//方法一 a := Node{1, 2}//方法二 ptr *Node = new(Node) (*ptr).X = 1 (*ptr).Y = 2 //-------- func main() {var ptr *Node = new(Node)(*ptr).X = 1(*ptr).Y = 2fmt.Println(*ptr)} //---底层自动识别,这样也可以 func main() {var ptr *Node = new(Node)ptr.X = 1ptr.Y = 2fmt.Println(*ptr) } //方法三 var node *Node = &Node{}
-
结构体的所有字段在内存中是连续的
-
两个结构体的字段类型完全相同的话可以强制类型转换
-
用type struct1 struct2给struct2取别名,相当于定义了一个新的类型,两者之间可以强制类型转换,但是不能直接赋值
-
struct的每个字段上可以写上一个tag,该tag可以通过反射机制获取,常见于序列化和反序列化
-
复习一个点,Go没有public和private,所以用首字母大写和小写来确定是公共的还是私有的
-
序列化:对象转json字符串
-
反序列化:json字符串转对象
-
动手
-
import ("encoding/json""fmt" )type Node struct {Name stringPassword string }func main() {a := Node{"aaaaaa","123456",}str, _ := json.Marshal(a)fmt.Println(a)fmt.Println(string(str)) } //输出 //{aaaaaa 123456} //{"Name":"aaaaaa","Password":"123456"}
-
但是这种大写的变量很多客户端不习惯,所以使用tag,如果使用小写,就无法被结构体外部函数使用,无法序列化
-
import ("encoding/json""fmt" )type Node struct {Name string `json:"name"`Password string `json:"password"` }func main() {a := Node{"aaaaaa","123456",}str, _ := json.Marshal(a)fmt.Println(a)fmt.Println(string(str)) } //输出 //{aaaaaa 123456} //{"name":"aaaaaa","password":"123456"}
方法
-
方法是作用在指定类型上的函数
-
func (a Node) ok() {fmt.Println("ok") } //这个函数绑定给了Node //调用 var a Node p.ok()
-
动手
-
import ("fmt" )type Node struct {Name string `json:"name"`Password string `json:"password"` }func (a Node) ok() {fmt.Println(a.Name) } func main() {a := Node{"aaaaaa","123456",}a.ok() } //输出了Node的名字
-
这个方法只能用指定类型来调用,不能直接调用
-
如果想要修改原来的参数,我们使用结构体指针,并且这更常用,不用深拷贝,速度更快
-
type Node struct {Name string `json:"name"`Password string `json:"password"` }func (a *Node) ok() {a.Name = "bbbb" } func main() {a := Node{"aaaaaa","123456",}a.ok()//编译器底层自动识别变为&afmt.Println(a) }
-
可以通过实现String方法,可以自定义格式化输出
工厂模式
- 类似于构造函数,在结构体所在包下写相应构造的函数,返回结构体指针,这样就可以在结构体私有的情况下,在其他包调用这个函数直接返回结构体对象
Go学习-Day6
- 个人博客:CSDN博客
封装
- 类似java的类的封装,这里我们利用大小写和工厂模式来实现封装的功能
- 略过
继承
-
相似的类具有相似的方法,反复绑定相同的方法,代码冗余,所以引入了继承的概念
-
嵌套匿名结构体来实现继承的效果
-
动手实践!
-
type Node struct {Name string `json:"name"`Password string `json:"password"` }type Point struct {NodeX intY int }func (a *Node) ok() {a.Name = "bbbb" } func main() {var a Point = Point{Node{"aaa", "bbb"},1,2,}a.ok()fmt.Println(a) }
-
注意看,a.ok()其实是a.Node.ok()底层自动识别,可以省略匿名结构体
-
基本数据类型可以匿名,但是不能出现多个相同类型的匿名基本类型
接口
-
多态主要就是由接口来实现的
-
声明
-
type inter interface {a()b() }
-
实现接口
-
import "fmt"type inter interface {a()b() } type obj1 struct { }type union struct { }func (o obj1) a() {fmt.Println("okkk")//用node对象给接口的a()实现 }func (o obj1) b() {fmt.Println("ohhh") }func (u union) ok(in inter) {in.a()in.b() }func main() {x := union{}y := obj1{}x.ok(y) }
-
从上面我们可以看到,在声明一个接口之后,我们用工厂方法法obj对应接口的所有方法给实现了,然后另外整一个抽象类似的的结构体,绑定一个方法运行接口,这样我们就能通过接口把这两个类给链接在一起。
-
总结。
-
interface类型可以定义一组方法,方法不用实现,并且不能含有变量
-
Go语言没有implements关键字,只要一个变量类型,绑定了接口中所有的方法,这个变量就能实现这个接口
-
这个变量就能导入到含有接口作为变量的函数内
Go学习-Day7
- 个人博客:CSDN博客
断言
type Node struct {x inty int
}func main() {var a interface{}var n Node = Node{1, 2}a = nvar b Nodeb = a.(Node)fmt.Println(b)
}
-
此处我们有一个结构体n给空接口a赋值(空接口没有方法,相当于方法被n给完全实现,所以是可以赋值给接口的),然后我们想把接口赋值给具体的结构体对象,但是,这里会报错,需要使用类型断言的语法。用.(类型)类声明a的类型。
-
类型断言之后,编译器会判断这个变量是否是指向这个类型,如果是,就转换成这个类型来赋值
-
就是把抽象的接口转换成具体的类型的方法
-
如果类型不匹配的话,就会报panic,通过这个方法,可以判断接口个具体类型,执行特定操作
-
if a, flag := u.(xxx); flag == true {xxxx }
-
x.(type)会返回类型
文件
打开/关闭文件
import ("fmt""os"
)func main() {f, err := os.Open("E:\\JetBrains\\GoLandSpace\\src\\go_code\\project01\\main\\test.txt")if err != nil {fmt.Println(err)}fmt.Printf("%v", f)err = f.Close()if err != nil {fmt.Println(err)}
}
//返回&{0xc00010c780}
- 先用os包打开文件,再用方法关闭,上面的绝对路径当中,如果用正斜杠要双写,反斜杠就不用
读取文件
-
import ("bufio""fmt""io""os" )func main() {f, err := os.Open("E:/JetBrains/GoLandSpace/src/go_code/project01/main/test.txt")if err != nil {fmt.Println(err)}defer f.Close() //函数推出的时候自动关闭reader := bufio.NewReader(f) //创建一个缓冲区来读入for {str, err := reader.ReadString('\n') //读到换行符就停止if err == io.EOF {break //读到末尾}fmt.Println(str)}fmt.Println("-------------------") }
-
首先打开文件,然后按行读取,注意读到EOF要结束死循环
-
ioutil.ReadFile可以一次性读入到一个字节数组内,不过文件需要比较小的情况下使用
写入文件
import ("bufio""fmt""os"
)func main() {filePath := "E:/JetBrains/GoLandSpace/src/go_code/project01/main/a.txt"file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)//后面那个int在windows下无用if err != nil {fmt.Printf("%v", err)return}defer file.Close()str := "ok\n"writer := bufio.NewWriter(file)writer.WriteString(str)writer.Flush() //从缓冲区压入文件}
- 写入文件的流程也是非常亲切
- 我们写入写入缓冲区之后,我们必须要flush刷新一下缓冲区,将缓冲区内的字符刷到磁盘上
- 通过不同的标识符,有追加,截断清除的写入操作
- 类似的可以拷贝文件,有io.Copy()函数可以更方便地拷贝文件
命令行参数解析
Args
- os.Args这是go的命令行参数的数组,但是不方便
flag包
flag.StringVar(&xxx, "x", "", "sss")
//第一个变量是传入的值所存的变量的地址,"x"是传入的参数的名字,""是默认值, "sss"是说明
- 设置完这些变量之后使用flag.parse()来解析命令行的参数
JSON
- JSON(JavaScript Object Notation)是一种轻量级的数据交换格式 key-val的形式
- 中括号表示数组,大括号表示对象,一个个的键值对用,号分隔开
- 序列化方法上面写过了,用json.Marshal()
- 反序列化用json.Unmarshal([]byte(str), &xxx)
Go学习-Day8
- 个人博客:CSDN博客
单元测试
-
testing框架会将xxx_test.go的文件引入,调用所有TestXxx的函数
-
在cal_test.go文件里面写这个
-
package mainimport "testing"func TestAdd(t *testing.T) {a, b := 1, 2if add(a, b) != 4 {t.Fatalf("Wrong Answer!")} }
-
在cal.go文件里写这个
-
package mainfunc add(a int, b int) int {return a + b }
-
运行go test -v的命令,就能运行单测
-
可以得到结果
-
=== RUN TestAddcal_test.go:8: Wrong Answer! --- FAIL: TestAdd (0.00s)
-
testing框架import这个test文件之后,会调用所有TestXxx的函数,注意大写!
Goroutine
进程和线程
- 进程是程序的在操作系统的一次执行过程
- 线程是比进程更小的单位,一个进程能创建销毁多个线程
- 一个程序至少有一个进程,一个进程至少有一个线程
并发和并行
- 多线程在单核上运行,就是并发
- 多线程在多核上运行,就是并行
Go协程和主线程
-
主线程类似进程
-
协程类似线程,是轻量级的线程
-
协程的特点
- 有独立的空间
- 共享程序的堆空间
- 调度由用户控制
- 协程是轻量级的线程
-
import ("fmt""strconv""time" )func test() {for i := 0; i < 5; i++ {fmt.Println("test() calls! " + strconv.Itoa(i))time.Sleep(time.Second)} }func main() {go test()for i := 0; i < 5; i++ {fmt.Println("main() calls! " + strconv.Itoa(i))time.Sleep(time.Second)} }
-
输出
-
main() calls! 0 test() calls! 0 test() calls! 1 main() calls! 1 main() calls! 2 test() calls! 2 test() calls! 3 main() calls! 3 main() calls! 4 test() calls! 4
-
go关键字会另起一个协程,主线程执行到这里会开一个协程并行执行,如果主线程执行完毕退出,协程会被强制退出
MPG模式
-
M(Machine)是操作系统的主线程,也就是物理线程
-
P(Processor)协程执行的上下文
-
G(Gorountine)协程
-
Go语言的协程是轻量级的,是逻辑态的,可以起上万个协程;而C/java的多线程是内核态的,几千个就会耗光CPU
CPU相关
runtime.NumCPU()
//获取本地CPU数目
runtime.GOMAXPROCS(int)
//设置GO最大可用的CPU数目
//Go Max Processors
协程并行的资源竞争
-
多个协程同时访问一个资源会发生冲突,会发生并发问题
-
在java中我们有锁和原子类来保证并发安全
-
声明一个全局锁变量lock
-
lock sync.Mutex //sync是同步的意思,Muti-excluded互斥锁?
-
lock.Lock()//在进行并发的读写操作的时候,先上个锁 ...//在进行操作的时候,别的协程会排队等待 lock.Unlock()//解锁之后,才能给别的协程使用
-
主线程读的时候也需要加锁,因为底层不知道协程已经解锁了,会发生资源冲突
-
但是这样不同协程之间没办法通讯,不知道什么时候协成完成任务了,白白空转浪费时间,或者提前结束主线程,终止协程,管道可能能解决这些问题,明天再学
Go学习-Day9
- 个人博客:CSDN博客
Channel
- Channel本质是一个队列
- 多goroutine访问时不需要加锁,Channel天然线程安全
- channel有类型,只能写入相同类型
- channel是引用类型
- channel必须初始化才能写入数据,make分配内存
声明
-
var intChan chan intintChan = make(chan int, 3)
-
java不是很熟悉,感觉chan有点像java的原子类
存入取出
-
intChan<- xxx //存入 a := <= intChan//取出
-
管道不会自然增长,不能超过容量,不能从空的管道里取出数据,会上DeadLock
-
如果想要存储任意类型的管道,可以用空借口
-
var allChan chan interface{}
-
但是,取出的时候注意类型断言
-
close(intChan)
-
channel关闭之后就不能再写入了,但是能继续读出
-
关闭之后能用for-range来遍历,如果不关闭的话会出现死锁
-
死锁的情况很多,建议多找几篇文章看看,写写实操一下
-
空的缓冲chan相当于无缓冲的chan,无缓冲的chan需要接收者,传入者,否则就会死锁,注意及时关闭
-
只向管道内写入,不读取就会deadlock,读得慢没有关系
-
关键是要给每个管道安排一个发送者,和接收者!!!
一个简单的死锁分析
package mainimport ("fmt""time"
)func write(intChan chan int) {for i := 0; i < 5; i++ {fmt.Println("写入: ", i)intChan <- itime.Sleep(time.Second)}//close(intChan)
}func read(intChan chan int, exitChan chan bool) {for {val, ok := <-intChanif !ok {break}fmt.Println("读到", val)}exitChan <- trueclose(exitChan)
}
func main() {intChan := make(chan int, 20)exitChan := make(chan bool, 1)go write(intChan)go read(intChan, exitChan)for {_, ok := <-exitChanif !ok {break}}
}
- 输出
写入: 0
读到 0
写入: 1
读到 1
写入: 2
读到 2
写入: 3
读到 3
写入: 4
读到 4
fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan receive]:
main.main()E:/JetBrains/GoLandSpace/src/go_code/project01/main/hello.go:36 +0xe8goroutine 7 [chan receive]:
main.read(0x0?, 0x0?)E:/JetBrains/GoLandSpace/src/go_code/project01/main/hello.go:19 +0x99
created by main.mainE:/JetBrains/GoLandSpace/src/go_code/project01/main/hello.go:33 +0xd9Process finished with the exit code 2
- 下面是个人的分析,不一定对,有大佬可以来指正
- 如果我们不close,channel是可以读的,我们可以边读,边写,并且,读的速度是可以更慢或者更快的,go底层会通过上下文自行判断。
- 但是这里,我们写的协程,我们关闭channel,在程序运行完之后自行关闭,此时我们读的协程会卡在intChan,等待读入,但是此时还不会报错,因为协程会因为主线程结束而结束。但是后面的exitChan会导致报错
package mainimport ("fmt""time"
)func write(intChan chan int) {for i := 0; i < 5; i++ {fmt.Println("写入: ", i)intChan <- itime.Sleep(time.Second)}//close(intChan)
}func read(intChan chan int, exitChan chan bool) {for {val, ok := <-intChanif !ok {break}fmt.Println("读到", val)}fmt.Println("到了这里")//exitChan <- true//close(exitChan)
}
func main() {intChan := make(chan int, 20)exitChan := make(chan bool, 1)go write(intChan)go read(intChan, exitChan)time.Sleep(time.Second * 10)//for {// _, ok := <-exitChan// if !ok {// break// }//}
}
-
这样并没有报错,并且发现到了这里没有打印,说明read函数作为intChan的接收者一直在等待,这时候。
-
但是,主线程运行到下面的for的时候,此时exitChan是空的,因为intChan一直在死循环等待,所以触发了死锁
-
只读只写
-
var chanIn chan<- int//只写
-
var chanOut <-chan int//只读
-
select {case …}可以安全地取出数据
-
使用recover捕获协程终端 panic
Go学习-Day10
- 个人博客:CSDN博客
反射
-
编写函数适配器,序列化和反序列话可以用到
-
反射可以在运行时,动态获取变量的各种信息,例如类型,结构体本身的信息,修改变量的值,调用关联的方法
-
反射是不是和映射相反?是一种逆函数?
-
变量到空接口相互转换,空接口和reflect.value相互转换
-
动手一下
-
import ("fmt""reflect" )func test(a interface{}) {b := reflect.TypeOf(a)fmt.Println(b) }func main() {var a int = 10test(a) }
-
打印 “int”
-
reflect.TypeOf()//从接口获取原类型 reflect.ValueOf()//从接口获取reflect.Value类型.Int能取到具体的类型 //如果需要原类型,需要类型断言 reflect.Interface//把reflect.Value转换成空接口
-
Kind是大的种类,Type是小的类型
-
常量在定义的时候必须初始化
-
reflect.Value.Kind返回的是常量
-
如果传入指针类型的话(反射常常需要改变原来的值)指针类型需要.Elem方法取到值,再用.SetInt之类的方修改原来的值
-
Value//指reflect.Value Value.NumField()//获取字段数 Value.Field()//根据下标,获取第几个字段,返回的也是relect.Value Tpye//指reflect.Type Tpye.Field().Tag.Get("key")//可以获取tag,键值是结构体里面设置的例如,"json:"的key就是json,序列化反序列化的键值固定取json,其实可以自定义 Value.NumMethod()//获取方法数 Value.Method().Call(...)//获取第几个方法,然后调用 //这个顺序是按照函数名字典序排列的,Call传的是Value切片,返回的也是Value切片 //输入的时候需要定义一个Value切片,用reflect.ValueOf(xx)插入这个切片 Value.Elem().Field().SetXxx//修改字段 ...FieldByName()//可以用字段名来找 Value.New()//为指针申请空间,可以通过反射来创建类型
网络编程
- Golang的主要设计目标之一就是面向大规模的后端服务程序,网络通信是服务端程序必不可少的一部分
- 网络编程有两种 TCP(Transmission Control Protocol) socket编程和HTTP编程(建立在前者之上)
- 做服务器尽量少开端口,一个端口只能被一个程序监听
监听端口小Demo
-
net包提供了可以指的I/O接口
-
package mainimport ("fmt""net" )func main() {fmt.Println("开始监听")//使用tcp协议,监听本机listen, err := net.Listen("tcp", "0.0.0.0:8888")if err != nil {fmt.Println("err=", err)}//延迟关闭defer listen.Close()//循环等待for {//等待客户端连接fmt.Println("等待连接...")//获取连接conn, err := listen.Accept()if err != nil {fmt.Println("err=", err)} else {fmt.Println("con=", conn)}//起一个协程为客户端服务} }
-
用telnet呼叫一下 telnet 127.0.0.1 8888
-
开始监听 等待连接... con= &{{0xc00010ec80}} 等待连接... //返回
客户端
-
conn, err := net.Dial("tcp", "ip...:端口") //获取连接 //Dial是拨号的意思
-
通过端口就能和对应的程序进行交流
-
func main() {conn, err := net.Dial("tcp", "127.0.0.1:8888")if err != nil {fmt.Println("err=", err)}fmt.Println("连接成功conn=", conn) } //注意此时要开着上面的监听程序 //输出 连接成功conn= &{{0xc00010ca00}}
发送&&接收
server.go
package mainimport ("fmt""net"
)func process(conn net.Conn) {//连接过多不关闭的话就会导致其他连接无法成功defer conn.Close()for {buf := make([]byte, 512)//如果没有Write会停在这里,类似我们stdin输入的时候,光标会停在输入的位置//如果连接突然中断的话,这里会报错//TCP底层会定时发送消息,检查连接是否存在n, err := conn.Read(buf)if err != nil {fmt.Println("err=", err)return//有可能是关闭了}//字节切片要强制转换//buf后面的存的可能是乱七八糟的东西,注意取前n个!fmt.Print(string(buf[:n]))}
}func main() {fmt.Println("开始监听")//使用tcp协议,监听本机listen, err := net.Listen("tcp", "0.0.0.0:8888")if err != nil {fmt.Println("err=", err)}//延迟关闭defer listen.Close()//循环等待for {//等待客户端连接fmt.Println("等待连接...")//获取连接conn, err := listen.Accept()if err != nil {fmt.Println("err=", err)} else {fmt.Println("con=", conn)}//起一个协程为客户端服务go process(conn)}
}
client.go
package mainimport ("bufio""fmt""net""os"
)func main() {conn, err := net.Dial("tcp", "127.0.0.1:8888")if err != nil {fmt.Println("err=", err)}fmt.Println("连接成功conn=", conn)//创建标准stdin的readerreader := bufio.NewReader(os.Stdin)//读取一行str, err := reader.ReadString('\n')if err != nil {fmt.Println("err=", err)}n, err := conn.Write([]byte(str))if err != nil {fmt.Println("err=", err)}fmt.Println("发送了n个字节n=", n)
}
- 一个小点,发送的字节数多2,应该是回车键的缘故,可能这里是当成\n\r
相关文章:
Go学习[合集]
文章目录 Go学习-Day1Go学习-Day2标识符变量基础语法字符串类型类型转换string和其他基本类型转换其他类型转stringstring转其他类型 指针类型运算符标准IO分支语句 Go学习-Day3循环语句函数声明init函数匿名函数闭包defer Go学习-Day4函数值传递,引用传递常用的函数…...

代码随想录算法训练营第42天 | ● 01背包问题,你该了解这些! ● 01背包问题,你该了解这些! 滚动数组 ● 416. 分割等和子集
文章目录 前言一、01背包问题,你该了解这些!二、01背包问题,你该了解这些! 滚动数组三、416. 分割等和子集总结 前言 01背包 一、01背包问题,你该了解这些! 确定dp数组以及下标的含义 对于背包问题&#x…...

解决DNS服务器未响应错误的方法
当你将设备连接到家庭网络或具有互联网接入功能的Wi-Fi热点时,由于各种原因,互联网连接可能无法正常工作。本文中的说明适用于Windows 10、Windows 8和Windows 7。 无法连接到DNS服务器的原因 故障的一类与域名系统有关,域名系统是世界各地互联网提供商使用的分布式名称…...

SpringBoot的HandlerInterceptor拦截器使用方法
一、创建拦截器 通过实现HandlerInterceptor接口创建自己要使用的拦截器 import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.…...
java 常用 jar 包
1 Lombok 是一个 java 类库,它利用注解方式自动生成 java bean 中 getter、setter、equals 等方法,还能自动生成 logger、toString、hashCode、builder 等 日志相关变量、Object 类方法或设计模式相关的方法,能够让你的 代码更简洁࿰…...

C#面试十问
1:C#中变量类型分为哪两种?它们的区别是什么?2:Class和Struct的区别?3:C#中类的修饰符和类成员的修饰符有哪些?4:面向对象的三个特征(特点)是什么?…...

Day 41 动态规划part03 : 343. 整数拆分 96.不同的二叉搜索树
96. 不同的二叉搜索树 给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。 示例 1: 输入:n 3 输出:5示例 2: 输入:n 1…...

四轴飞行器的电池研究(MatlabSimulink仿真)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

准备HarmonyOS开发环境
引言 在开始 HarmonyOS 开发之前,需要准备好开发环境。本章将详细指导你如何安装 HarmonyOS SDK、配置开发环境、创建 HarmonyOS 项目。 目录 安装 HarmonyOS SDK 配置开发环境 创建 HarmonyOS 项目 总结 1. 安装 HarmonyOS SDK HarmonyOS SDK 是开发 Harmo…...

Java 面试 - Redis
Redis Redis 是基于键值对的非关系型数据库。Redis 拥有string、hash、list、set、zset等多种数据结构, redis具有惊人的读写性能, 其优秀的持久化机制是的它在断电和机械故障时也不会发生数据丢失, 可以用于热点数据存放, 还提供了键过期、发布订阅、食物、流水线、LUA脚本等多…...

【Go 基础篇】Go语言结构体之间的转换与映射
在Go语言中,结构体是一种强大的数据类型,用于定义和组织不同类型的数据字段。当我们处理复杂的数据逻辑时,常常需要在不同的结构体之间进行转换和映射,以便实现数据的转移和处理。本文将深入探讨Go语言中结构体之间的转换和映射技…...

Java 多线程系列Ⅳ(单例模式+阻塞式队列+定时器+线程池)
多线程案例 一、设计模式(单例模式工厂模式)1、单例模式2、工厂模式 二、阻塞式队列1、生产者消费者模型2、阻塞对列在生产者消费者之间的作用3、用标准库阻塞队列实现生产者消费者模型4、模拟实现阻塞队列 三、定时器1、标准库中的定时器2、模拟实现定时…...

将 ordinals 与 比特币智能合约集成 : 第 1 部分
将序数与比特币智能合约集成:第 1 部分 最近,比特币序数在区块链领域引起了广泛关注。 据称,与以太坊 ERC-721 等其他代币标准相比,Ordinals 的一个主要缺点是缺乏对智能合约的支持。 我们展示了如何向 Ordinals 添加智能合约功…...

【USRP】集成化仪器系列1 :信号源,基于labview实现
USRP 信号源 1、设备IP地址:默认为192.168.10.2,请勿 修改,运行阶段无法修改。 2、天线输出端口是TX1,请勿修改。 3、通道:0 对应RF A、1 对应 RF B,运行 阶段无法修改。 4、中心频率:当需要…...

串行协议——USB驱动[基础]
多年前的学习记录,整理整理。 一、USB协议基础 二、Linux内核USB驱动源码分析 USB中不同类型设备使用的 设备描述符(设备类\设备子类\设备协议) 配置不同,典型的以下几种:1)HID设备: Human Input Device人工输入设备, 如鼠标\键盘\游戏手柄等.2)CDC设备: Communi…...

健康舒适的超满意照明体验!SUKER书客SKY护眼台灯测评
健康舒适的超满意照明体验!SUKER书客SKY护眼台灯测评 2022年全国儿童青少年总体近视率为53.6%,其中6岁儿童为14.5%,小学生为36%,初中生为71.6%,高中生为81%,近视已成为当下人们遇到的比较普遍的眼健康问题…...

PID 算法
1.1 概述 比例(Proportion)积分(Integral)微分(Differential)控制器(PID控制器或三项控制器)是一种采用反馈的控制回路机制,广泛应用于工业控制系统和需要连续调制控制的…...

13.Redis 事务
Redis 事务 redis 事务事务操作multi 开启事务exec 执行事务discard 放弃当前事务watchunwatch redis 事务 Redis 的事务和 MySQL 的事务概念上是类似的。 都是把⼀系列操作绑定成⼀组。 让这⼀组能够批量执⾏。 Redis 的事务和 MySQL 事务的区别: 弱化的原⼦性: 这里指的是 …...
李宏毅机器学习课程笔记(更新ing)
CNN 为什么AlphaGo可以用CNN?棋盘抽象成图片时需要注意什么? 首先图片有两个特点: 1,只观察局部就可以显示某种pattern,比如要得出一个鸟嘴的结论,只需要观察局部图片 2,某种pattern可以出现在图…...

SIP mini 对讲终端,带sip热点功能
SV-A10/SV-A10W SIP mini 对讲终端,带sip热点功能 SV-A10/SV-A10W 是专门针对行业用户需求研发的一款 SIP mini 对讲产品,外观小巧,功能 强大,集智能安防、音/视频对讲和广播功能于一体,性价比高。支持壁挂式安装/86…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
C++.OpenGL (20/64)混合(Blending)
混合(Blending) 透明效果核心原理 #mermaid-svg-SWG0UzVfJms7Sm3e {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-icon{fill:#552222;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-text{fill…...
人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent
安全大模型训练计划:基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标:为安全大模型创建高质量、去偏、符合伦理的训练数据集,涵盖安全相关任务(如有害内容检测、隐私保护、道德推理等)。 1.1 数据收集 描…...