![](https://img-blog.csdnimg.cn/ca6e1c26d63a49ef941f5dd96eebad57.png?x-oss-process=image/resize,m_fixed,h_224,w_224)
golang学习笔记
本专栏为《go程序设计语言》的读书笔记
老朱.
业余时间喜欢写点技术博客,读点无用之书。想阅读我更多原创的非技术类文章,可以关注我的公众号 老朱的读书随想
展开
-
如何批量取消多个goroutine
goroutine 无法自动终结,如果一个任务可以分片进行(而不是一个IO走到黑),那么可以借助select来监听一个取消通道。如果对于单个goroutine或固定数量的多个goroutine,我们只需向取消通道中发送等量的事件即可。但对于不确定数量的goroutine,如何保证都通知到他们呢?可以借助通道的关闭特性,当通道关闭后,从通道接收消息额操作会立刻返回。也就是说,如果想向不确定数量的一批goroutine发送一个事件,且这种事件是一次性的,不会重复发送,可以让所有goroutine监听一个通道原创 2021-11-14 21:36:36 · 1082 阅读 · 0 评论 -
golang中的计时器与select
golang中的select机制让我对大师的设计水平赞叹不已,它基于多个阻塞的通道操作(发送或接收),哪个可以执行,就执行哪个。select {case <- ch1: // ...case x <- ch2: // use x ...case ch3 <- y: // ...}前两个case是接收,第三个case是发送。golang的运行机制是,当前的goroutine会阻塞,当ch1或ch2可以接收事件时,或者ch3可以发送事件时,就触发对应的case代码块,然后s原创 2021-11-14 21:35:40 · 1686 阅读 · 0 评论 -
一个使用golang并行计算的例子
sync.WaitGroup的工作方式类似java里面的CountDownLatch,用于计数并阻塞等待。下面的例子,我们来编写一个并行任务,以并发的方式处理一个批次的任务,可以控制它的最大并发度,并在所有任务全部完成后返回一个结果。import ( "fmt" "sync" "time" "unicode/utf8")func concurrentPrinting(tasks []string, maxConcurrent int) int { tokens := make(chan原创 2021-11-14 21:34:22 · 1344 阅读 · 1 评论 -
用缓冲通道实现阻塞队列
当通道(channel)没有指定最大长度(cap)时,它的最大长度是0,也就意味着发送操作将会阻塞,直到另一个goroutine在对应的通道上执行了接收操作,倒过来说也是成立的,接收操作将会阻塞,直到另一个goroutine在对应的通道上执行发送操作。也就是说,基于无缓冲队列的发送和接收是同步进行的,而基于缓冲通道的原理是,如果通道没有满,发送操作会立刻完成,如果通道内还有消息,接收操作也会立刻完成,否则,它们才会阻塞。创建缓冲通道的方法make(chan int, 3)上面的代码创建了一个cha原创 2021-11-14 21:32:50 · 307 阅读 · 0 评论 -
goroutine与channel
启动goroutine在golang中,一个活动单元被称为goroutine,通过go关键字来启动一个goroutine并立刻返回。f() // 调用f(),并等待结果go f() // 新建一个调用f()的goroutine,不用等待main函数运行在主goroutine中,当main函数返回时,所有的goroutine都将终结,进程将退出,释放所有资源。goroutine的粒度是一个函数,这个函数无需实现任何接口,也无需绑定到任何类中。我们也可以通过go关键字直接运行一个匿名函数,就像下面原创 2021-11-14 21:29:47 · 329 阅读 · 0 评论 -
使用golang实现一个简单地httpServer
golang的标准库中,可以使用http.ListenAndServe来启动一个httpServerfunc ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe()}它接收两个参数,第一个是寄宿的地址,第二个是http.Handler接口。我们就来定义一个类来实现这个接口。typ原创 2021-11-14 21:28:10 · 1031 阅读 · 0 评论 -
golang中的排序
golang中排序的底层逻辑golang中排序最底层的逻辑是是这段代码sort.Sort// Sort sorts data.// It makes one call to data.Len to determine n and O(n*log(n)) calls to// data.Less and data.Swap. The sort is not guaranteed to be stable.func Sort(data Interface) { n := data.Len() qu原创 2021-11-10 10:40:13 · 768 阅读 · 0 评论 -
golang中通过flag获取命令行参数
golang中的flag提供了一种基于命令行的、开箱即用的、提取命令行参数的方式。package mainimport ( "flag" "fmt")func main() { wordPtr := flag.String("word", "foo", "a string") numPtr := flag.Int("num", 123, "a number") flag.Parse() fmt.Printf("word = %s\n", *wordPtr) fmt.Printf原创 2021-11-10 10:39:24 · 1449 阅读 · 0 评论 -
golang中的接口
接口类型的定义与实现接口是一种抽象类型,它仅包含一些方法签名,没有给出具体实现。接口意味着约定。你拿到一个接口以后,你可以通过查看它包含的方法来知道它能做什么。type Speaker interface { Speak() string}上面的代码定义了一个名为Speaker的接口,它包含一个方法。我们能从中看出来,Speak方法不需要提供参数就可以获取一个字符串,好像是要说点什么。type Employee struct { ID int Name, Addre原创 2021-11-10 10:38:07 · 423 阅读 · 0 评论 -
golang中的面向对象
声明方法声明方法的语法和声明普通函数非常类似,只是在函数名字前面加上一个参数,这个参数把这个方法绑定到它对应的类型上。func (e Employee) ToString() (description string) { return fmt.Sprintf("[%d, %s], from %s", e.ID, e.Name, e.Address)}注意这里的(e Employee),这里的Employee说明了这个ToString()方法属于Employee类型的方法,而e就类似其它语言中的t原创 2021-11-10 10:29:35 · 1026 阅读 · 0 评论 -
golang中的defer
在golang中弱化了异常机制,defer是一种finally的实现机制,它的工作原理是这样的它加在一个函数调用前面,当执行到这一行时,它会计算参数表达式的值,但并不立刻执行函数调用;当函数结束运行时,开始执行defer的函数调用;如果在一个函数中出现了多个defer语句,那么当函数运行结束时,按照出栈顺序来执行它们;这样做的好处是,我们仿佛可以将finally语句块中的代码段,以更小的粒度、一行一行地穿插到主逻辑的不同位置,并且事后按照当时的参数去调用。func accumulate(cou原创 2021-11-10 10:27:49 · 1233 阅读 · 0 评论 -
golang中的函数
函数的语法func name(param-list) (result-list){ body}实例// 比较直接的语法func add1(x int, y int) int { return x + y}// 同类型的参数(或返回值)可以放在一起,最后面声明一次类型就好func add2(x, y int) int { return x + y}// 将返回值也赋予一个变量名,可读性更好func add3(x, y int) (z int) { z原创 2021-11-10 10:26:59 · 197 阅读 · 0 评论 -
golang中使用JSON
golang标准库有一个名叫encoding/json的包,包含了JSON的序列化(Marshal)和反序列化(Unmarshal)的能力。通过成员标签控制JSON输出type Movie struct { Title string Year int `json:"released"` Color bool `json:"color,omitempty"` Actors []string}成员标签是一组有空格分开的键值对,格式为key:"value"。键j原创 2021-11-10 10:26:04 · 5141 阅读 · 0 评论 -
golang中的struct
struct是实现面向对象的重要技术,基本上都跟类型声明type name underlying-type结合使用。struct是值类型,所以它的零值是所有成员的零值。由于值类型在作为函数参数时的局限性,所以经常配合指针一起使用。声明type Employee struct { ID int Name string Address string}一行一个成员,中间没有逗号或分号,大写的成员可以在包外访问。如果类型相同,也可以考虑定义在一行,例如type原创 2021-11-10 10:25:03 · 760 阅读 · 0 评论 -
golang中的map
在golang中,map是散列表的引用,也就是说它是引用类型,像指针那样。map的类型是map[K]V,所有元素的key都K类型,所有元素的value都是V类型。初始化mapm := make(map[string]int) // 方式一m := map[string]int{} // 方式二避免对零值(未初始化)的map设置kv。var ages map[string]intages["tom"] = 12 // 异常var ages := map[string]int{}原创 2021-11-10 10:23:13 · 614 阅读 · 0 评论 -
golang中的slice
golang中的slice表示一个可变长度的数组,声明为 []T ,其中T是类型。 关于数组请参见:golang中的数组slice跟数组紧密相连,slice有三个属性,分别是:指针、长度(len)和容量(cap),其中指针指向底层数组,所以它天然依赖底层数组。从某种意义上来讲,slice更像一个结构体,这个结构体里面必然包含一个指针。slice的指针指向底层数组arr := [...]int{1, 2, 3, 4, 5}slice1 := arr[:3]for i := range slice1原创 2021-11-09 21:51:01 · 1304 阅读 · 0 评论 -
golang中的数组
golang中的数组实现的比较保守,长度固定,属于值类型。数组的长度是数组类型的一部分,比如 [3]int和[4]int都是长度固定的数组,但它们两个的类型是不同的,因为长度不同。[]int是slice,没有指定长度。数组的初始化数组的类型一定是包含长度的,比如下面这两个var arr [3]int = [3]int{1, 2, 3}arr2 := [3]int{1, 2, 3}也可以让编译器来推断长度arr3 := [...]int{1, 2, 3}如果显式指定的话,初始化值的时候还原创 2021-11-09 21:49:40 · 684 阅读 · 0 评论 -
golang的常量
常量是一种表达式,编译器会对它做一些增强支持,比如如果修改常量的话则会编译失败。定义常量const pi = 3.14如果定义多个常量const ( pi = 3.14 e = 2.718)通过常量生成器iota来定义枚举类型type Weekday intconst ( Sunday Weekday = iota Monday Tuesday Wednesday Thursday Friday Saturday原创 2021-11-09 21:48:39 · 3355 阅读 · 0 评论 -
golang的string
golang中的string是不可变的字节序列,零值是空字符串,默认是UTF-8编码。golang中使用字符串最需要注意的是,golang中的字符串是字节序列,string == []byte,由于一个字符占用的字节数不确定,所以无法通过下标[i]的方式稳定地获取对应位置的字符。 也就意味着字符串的第i个字节,不一定就是第i个字符,原因请参考 unicode与UTF-8文字符号在golang中被称为rune,发音为/ru:n/,意思是符号。由于utf-8最多使用4个字节来编码,所以rune类型是int3原创 2021-11-09 20:34:45 · 1284 阅读 · 0 评论 -
unicode与UTF-8
字符集要解决的第一个问题是描述文字,包括字符、数字和符号等。最开始的方案是ASCII,它的编码规则也很简单,一个code point占用一个字节。它由美国人发明,在英文环境下运行的很好。当计算机越来越普及时,人们发现ASCII字符集无法显示其它国家的语言文字,比如中文,于是各个国家就开始发明自己的编码,中国人发明了GB2312和GBK等,来解决中文的显示问题。为了避免各国自己造轮子,ISO看不下去了,搞了一个UNICODE字符集,直接解决了世界上所有的语言字符集问题。但是,另外一个问题随之而来,就是存储原创 2021-11-09 20:33:40 · 360 阅读 · 0 评论 -
golang的作用域
作用域与生命周期不是一个概念,作用域是变量或其它声明出现的区域,是一个在编译阶段关注的问题,而生命周期是一个变量能被其它部分所引用的起止时间,是一个运行时的问题。如果块级作用域内部的一个变量的指针被赋值给包级别的变量(俗称逃逸),那么这个变量的作用域还是在这个块内,但这个变量的值却会始终存在,你无法通过变量本身访问它,但可以通过外部的指针来访问它。golang是典型的块级作用域,块(block) 有不同的层级,最外层是全局块,里面有 int, len() 等,再然后有 包 / 文件 / 函数 / if /原创 2021-11-09 20:31:19 · 662 阅读 · 0 评论 -
golang的包和文件
golang中的包的作用与其他语言中的模块或库是类似的,主要用于支持模块化、封装、单独编译、重用等。一个包提供了一个独立的命名空间,大写开头的内容对外export。一个包下可以包含多个以.go结尾的源文件。包的导入import "example.com/common/util"在上面的导入语句中,example.com/common/util被称为导入路径(import path),它的源代码存在于$GOPATH/example.com/common/util目录下。这个包被导入以后,会绑定一个原创 2021-11-09 20:30:17 · 372 阅读 · 0 评论 -
golang的类型声明(type)
通过type关键字来定义一个新的命名类型,它基于一个已有的底层类型。新的类型的结构和行为由底层类型决定,但明确表明了它与底层类型不一样,所以不能混用。语法:type name underlying-type同样,如果类型的name以大写开头,则会导出,可以在包外访问。可以简单地这样理解:命名类型决定能不能通过编译,而底层类型决定实际运行结果。举例:// 两种基于 float64 底层类型的新的命名类型type Celsius float64type Fahrenheit float64/原创 2021-11-09 20:28:46 · 1354 阅读 · 0 评论 -
golang的指针
变量是值的名字或代号,指针是变量的地址。每个变量都有自己的地址。指针的本质是一个运算符(就像个函数一样),运算符的返回结果是内存地址。这个内存地址也可以赋值给一个指针类型的变量,如*int。对于指针类型的变量,有一个专属的操作符(*),返回值是某内存地址处存放的值。x := 1p := &x // &取内存地址,p就是一个 *int 类型的指针变量*p = 2 // *取内存地址指向的值fmt.Println(x) // 结果为2可以看出,借助指针,普通类型的变量也原创 2021-11-09 20:27:16 · 167 阅读 · 0 评论 -
golang的命名风格、声明、可见性和生命周期
可见性与scope如果在函数中声明,则仅在函数内有效;如果在函数外声明且小写开头,则在包内可见,即,在当前文件内可见,以及在同一个包内的其它源文件内可见;如果在函数外声明且大写开头,则设为export,对于包外可见和可用。没有所谓的public/private之类的关键字了。命名风格可见性越大,则命名可以越长越详细,可见性越小,命名越简单。使用驼峰命名法,例如 userName, 而不是 user_name。对于专用名词的缩写,常使用相同的大小写来表示,比如 htmlEscape,原创 2021-11-09 20:21:55 · 481 阅读 · 0 评论