推荐|Go入门(2):一个例子入门go的语法以及怎么使用goroutine+通道完成并发和同步

  • A+
所属分类:学习笔记

第二章学习笔记

 

Go语言的设计者们从编程效率出发设计了这门语言,但又不会丢掉访问底层程序结构的能力。设计者们通过一组最少的关键字、内置的方法和语法,最终平衡了这两方面。Go语言也提供了完善的标准库。标准库提供了构建实际的基于Web和基于网络的程序所需的所有核心库。

一.包

Go语言的每个代码文件都属于一个包,main.go也不例外。包这个特性对于Go语言来说很
重要.一个包定义一组编译过的代码,包的名字类似命名空间,可以用来间接访问包内声明的标识符。这个特性可以把不同包中定义的同名标识符区别开。

所有处于同一个文件夹里的代码文件,必须使用同一个包名。按照惯例,包和文件夹同名。

二.导入文件

main.go里的代码就可以引用search包里的Run函数。导入matchers包的时候,导入的路径前面有一个下划线。这个技术是为了让Go语言对包做初始化操作,但是并不使用包里的标识符。为了让程序的可读性更强,Go 编译器不允许声明导入某个包却不使用。下划线让编译器接受这类导入,并且调用对应包内的所有代码文件里定义的init函数。对这个程序来说,这样做的目的是调用matchers包中的rss.go代码文件里的init函数,注册RSS匹配器,以便后用。

与第三方包不同,从标准库中导入代码时,只需要给出要导入的包名。编译器查找包的时候,总是会到GOROOT和GOPATH环境变量引用的位置去查找。

三.变量

变量名matchers是以小写字母开头的。

在Go语言里,标识符要么从包里公开,要么不从包里公开。当代码导入了一个包时,程序可以直接访问这个包中任意一个公开的标识符。这些标识符以大写字母开头。以小写字母开头的标识符是不公开的,不能被其他包中的代码直接访问。但是,其他包可以间接访问不公开的标识符。例如,一个函数可以返回一个未公开类型的值,那么这个函数的任何调用者,哪怕调用者不是在这个包里声明的,都可以访问这个值。

map是Go语言里的一个引用类型,需要使用make来构造。如果不先构造map并将构造后的值赋值给变量,会在试图使用这个map变量时收到出错信息。这是因为map变量默认的零值是nil.

在Go语言中,所有变量都被初始化为其零值。对于数值类型,零值是0;对于字符串类型,零值是空字符串;对于布尔类型,零值是false;对于指针,零值是nil。对于引用类型来说,所引用的底层数据结构会被初始化为对应的零值。但是被声明为其零值的引用类型的变量,会返回nil作为其值。

四.search.go——展示如何使用goroutine

Run函数包括了这个程序最主要的控制逻辑。这段代码很好地展示了如何组织Go程序的代码,以便正确地并发启动和同步goroutine。

五.代码说明

a.Go语言使用关键字func声明函数,关键字后面紧跟着函数名、参数以及返回值。

b.Go语言,允许一个函数返回多个值。RetrieveFeeds函数第一个返回值是一组Feed类型的切片。切片是一种实现了一个动态数组的引用类型,在Go语言里可以用切片来操作一组数据。
使用关键字for range对feeds切片做迭代for _, feed := range feeds。关键字range可以用于迭代数组、字符串、切片、映射和通道。使用for range迭代切片时,每次迭代会返回两个值。第一个值是迭代的元素在切片里的索引位置,第二个值是元素值的一个副本。
第一个返回值使用下划线,其中下划线标识符的作用是占位符,占据了保存range调用返回的索引值的变量的位置。如果要调用的函数返回多个值,而又不需要其中的某个值,就可以使用下划线标识符将其忽略。

c.编译器使用函数返回值的类型来确定每个变量的类型。简化变量声明运算符只是一种简化记法,让代码可读性更高。这个运算符声明的变量和其他使用关键字var声明的变量没有任何区别。根据经验,如果需要声明初始值为零值的变量,应该使用var关键字声明变量;如果提供确切的非零值初始化变量或者使用函数返回值创建变量,应该使用简化变量声明运算符。
d.通道——在Go语言中,通道(channel)和映射(map)与切片(slice)一样,也是引用类型,不过通道本身实现的是一组带类型的值,这组值用于在goroutine之间传递数据。通道内置同步机制,
从而保证通信安全。
e.在Go语言中,如果main函数返回,整个程序也就终止了。Go程序终止时,还会关闭所有
之前启动且还在运行的goroutine。写并发程序的时候,最佳做法是,在main函数返回前,清理
并终止所有之前启动的goroutine。编写启动和终止时的状态都很清晰的程序,有助减少bug,防
止资源异常。使用sync包的WaitGroup跟踪所有启动的goroutine。非常推荐使用WaitGroup来
跟踪goroutine的工作是否完成。WaitGroup是一个计数信号量,我们可以利用它来统计所有的
goroutine是不是都完成了工作。
将WaitGroup变量的值设置为将要启动的goroutine的数量waitGroup.Add(len(feeds)。我们为每个数据源都启动了一个goroutine来处理数据。每个goroutine完成其工作后,就会递减WaitGroup变量的计数值(waitGroup.Done()),当这个值递减到0时,我们就知道所有的工作都做完了

f.匿名函数
使用关键字go启动了一个匿名函数作为goroutine。匿名函数是指没有明确声明名字的函数。匿名函数也可以接受声明时指定的参数。
指针变量可以方便地在函数之间共享数据。使用指针变量可以让函数访问并修改一个变量的状态,而这个变量可以在其他函数甚至是其他goroutine的作用域里声明。
在Go语言中,所有的变量都以值的方式传递。因为指针变量的值是所指向的内存地址,在函数间传递指针变量,是在传递这个地址值,所以依旧被看作以值的方式在传递。

g.闭包
Match(matcher, feed, searchTerm, results)
waitGroup.Done
WaitGroup的值没有作为参数传入匿名函数,但是匿名函数依旧访问到了这个值。
Go语言支持闭包,这里就应用了闭包。实际上,在匿名函数内访问searchTerm和results变量,也是通过闭包的形式访问的。因为有了闭包,函数可以直接访问到那些没有作为参数传入的变量。匿名函数并没有拿到这些变量的副本,而是直接访问外层函数作用域中声明的这些变量本身。因为matcher和feed变量每次调用时值不相同,所以并没有使用闭包的方式访问这两个变量。

h.常量
因为Go编译器可以根据赋值运算符右边的值来推导类型,声明常量的时候不需要指定类型。

i.关键字defer
defer file.Close()
关键字defer会安排随后的函数调用在函数返回时才执行。
在使用完文件后,需要主动关闭文件。使用关键字defer来安排调用Close方法,可以保证这个函数一定会被调用。哪怕函数意外崩溃终止,也能保证关键字defer安排调用的函数会被执行。关键字defer可以缩短打开文件和关闭文件之间间隔的代码行数,有助提高代码可读性,减少错误。

j.feed.so RetrieveFeeds
根据Decode方法的声明,该方法可以接受任何类型的值
Decode方法接受一个类型为interface{}的值作为参数。这个类型在Go语言里很特殊,一般会配合reflect包里提供的反射功能一起使用.
不需要对Decode调用之后的错误做检查。函数执行结束,这个函数的调用者可以检查这个错误值,并决定后续如何处理。

k.如何声明一个interface(接口)类型
type Matcher interface {
Search(feed *Feed, searchTerm string) ([]*Result, error)
}
interface关键字声明了一个接口,这个接口声明了结构类型或者具名类型需要实现的行为。一个接口的行为最终由在这个接口类型中声明的方法决定。

命名接口的时候,也需要遵守Go语言的命名惯例。
如果接口类型只包含一个方法,那么这个类型的名字以er结尾。例子里就是这么做的,所以这个接口的名字叫作Matcher。
如果接口类型内部声明了多个方法,其名字需要与其行为关联。
如果要让一个用户定义的类型实现一个接口,这个用户定义的类型要实现接口类型里声明的所有方法。

l.声明空结构类型
type defaultMatcher struct{}
空结构在创建实例时,不会分配任何内存。这种结构很适合创建没有任何状态的类型。对于默认匹配器来说,不需要维护任何状态,所以我们只要实现对应的接口就行。

m.最佳实践
func (m defaultMatcher) Search
如果声明函数的时候带有接收者,则意味着声明了一个方法。这个方法会和指定的接收者的
类型绑在一起。在我们的例子里,Search方法与defaultMatcher类型的值绑在一起。这意
味着我们可以使用defaultMatcher类型的值或者指向这个类型值的指针来调用Search方
法。无论我们是使用接收者类型的值来调用这个方,还是使用接收者类型值的指针来调用这个
方法,编译器都会正确地引用或者解引用对应的值,作为接收者传递给Search方法
调用方法的例子
// 方法声明为使用defaultMatcher类型的值作为接收者
func (m defaultMatcher) Search(feed *Feed, searchTerm string)
// 声明一个指向defaultMatcher类型值的指针
dm := new(defaultMatch)
// 编译器会解开dm指针的引用,使用对应的值调用方法
dm.Search(feed, "test")
// 方法声明为使用指向defaultMatcher类型值的指针作为接收者
func (m *defaultMatcher) Search(feed *Feed, searchTerm string)
// 声明一个defaultMatcher类型的值
var dm defaultMatch
// 编译器会自动生成指针引用dm值,使用指针调用方法
dm.Search(feed, "test")
因为大部分方法在被调用后都需要维护接收者的值的状态,所以,一个最佳实践是,将方法的接收者声明为指针。对于defaultMatcher类型来说,使用值作为接收者是因为创建一个defaultMatcher类型的值不需要分配内存。由于defaultMatcher不需要维护状态,所以不需要指针形式的接收者。

n.通道results 会一直被阻塞,直到有结果写入.一旦通道被关闭,for循环就会终止
for result := range results {
fmt.Printf("%s:\n%s\n\n", result.Field, result.Content)
}

ps:close(results ) 关闭通道

o.init方法
程序里所有的init方法都会在main函数启动前被调用

p.使用http包,Go语言可以很容易地进行网络请求。
resp, err := http.Get(feed.URI)

q.使用内置的append函数,将搜索结果加入到results切片里。append这个内置函数会根据切片需要,决定是否要增加切xa片的长度和容量。这个函数的第一个参数是希望追加到的切片,第二个参数是要追加的值。

六.小结

每个代码文件都属于一个包,而包名应该与代码文件所在的文件夹同名。

Go语言提供了多种声明和初始化变量的方式。如果变量的值没有显式初始化,编译器会将变量初始化为零值。

使用指针可以在函数间或者goroutine间共享数据。

通过启动goroutine和使用通道完成并发和同步。

Go语言提供了内置函数来支持Go语言内部的数据结构。

标准库包含很多包,能做很多很有用的事情。

使用Go接口可以编写通用的代码和框架。

发表评论

您必须才能发表评论!