Go语言基础学习三-简单的代码分析(并发)
有关于go语言基础学习的第二篇和第三篇都是直接通过分析代码来对go语言的一些特点进行认识,具体的针对性的一些go语言特性方面的细节会在之后的每一章节有细致的描述,我觉得学习一门语言,带着阅读代码的经验,从实际的语言使用中去有一个初步的认识,对于之后的每一章节语言特性细节上的学习会更加深入,所以,第二章和第三章的内容希望大家能够尽量理解这些代码的含义,我也是一个go语言方面的新手,和大家一起交流是最愉快的,如果实在不能理解也没关系,之后的章节会有针对性的讲解。
这一章节主要通过代码示例讲go语言的一个关键特性,那就是其充分利用现代计算机的多处理器和多核的功能,完全无需任何显示琐就可以写出许多并发程序。这在于go语言的两大特性使得使用它做并发编程非常轻松:
1.无需继承“线程” ,即可轻易创建goroutine(实际上是非常轻量级的线程)
2.通道为gorouting之间提供了类型安全的单向或双向通信,这也可以用来同步goroutine
go语言处理并发的方式是通过传递数据而非共享数据,这也就比传统的线程的锁方式来编写并发编程更为简单。
下面的示例是通过一个管道来获得从极坐标到笛卡尔坐标的转换。
首先介绍两个结构体:
type polar struct {
radius float64
θ float64//角度
}
type cartesian struct {
x float64
y float64
}
go语言的结构体是一种能够用来保存一个或多个数据字段的类型这些字段可以是内置类型(float64),结构体,接口(实际上就是一个指向任意类型的指针),因为go语言支持UTF-8编码,所以可以使用任何Unicode字符作为标示符,如角度。
const result = "Polar radius=%.02f θ=%.02f° → Cartesian x=%.02f y=%.02f\n"
var prompt = "Enter a radius and an angle (in degrees), e.g., 12.5 90, " +
"or %s to quit."
func init() {/*首先进入init()初始化程序,来根据不同的平台做不同的操作,runtime包提供了一个字符串常量GOOS来表示运行的操作系统常用值有:darwin,freebsd,linux,windows*/
if runtime.GOOS == "windows" {
/*若运行平台为windows,则输出按Ctrl+Z退出*/
prompt = fmt.Sprintf(prompt, "Ctrl+Z, Enter")
} else { // Unix-like
/*若运行平台为其他,则输出按Ctrl+D退出*/
prompt = fmt.Sprintf(prompt, "Ctrl+D")
}
}
通道:go语言中的通道是基于Unix上管道的思想而被设计出来的,它提供了双向或单向数据通信模式,通道的行为跟FIFO队列一样,通道中的数据不能被删除
1.通道的创建:message := make(chan string,10) 之前我们说过数据的映射,通道及切片都是通过make()函数来创建的,这里通道声明的语法为chan Type,make函数的第二个参数为通道缓冲区的大小,缓冲区满时会发生阻塞。
2.向通道写入数据:messages <- “Leader” 将“Leader”写入通道
3.获取通道数据:messages1 := <-messages 得到messages通道中的”Leader”
func main() {/*进入主程序main(),首先创建一个接收数据polar类型的通道*/
questions := make(chan polar)
defer close(questions)/*保证通道使用完毕后关闭通道*/
answers := createSolver(questions)/*根据问题通道创建相关的回答通道*/
defer close(answers)
interact(questions, answers)/*利用这两个相关的问题及回答通道,建立与用户的交互模型*/
}
func createSolver(questions chan polar) chan cartesian {/*根据问题通道创建相关的回答通道*/
answers := make(chan cartesian)/*创建回答通道*/
go func() {/*go fun()该go语句会创建一个异步goroutine来执行这个函数,return answer依然会执行,但同时该goroutine
也在异步执行等待通道数据的处理*/
for {
polarCoord := <-questions/*获取问题通道中的极坐标数据结构*/
θ := polarCoord.θ * math.Pi / 180.0 // degrees to radians
x := polarCoord.radius * math.Cos(θ)
y := polarCoord.radius * math.Sin(θ)
answers <- cartesian{x, y}/*经过对极坐标的数据处理得到结果,将结果写入回答通道*/
}
}()
return answers
}
func interact(questions chan polar, answers chan cartesian) {/*利用这两个相关的问题及回答通道,建立与用户的交互模型*/
reader := bufio.NewReader(os.Stdin)
fmt.Println(prompt)/*输出打印提示符提示用户输入什么*/
for {/*这里的for循环会持续让用户输入下一个极坐标*/
fmt.Printf("Radius and angle: ")
line, err := reader.ReadString(‘\n’)/*只读到一个转行符则退出程序*/
if err != nil {
break
}
var radius, θ float64
/*获得用户输入*/
if _, err := fmt.Sscanf(line, "%f %f", &radius, &θ); err != nil {
fmt.Fprintln(os.Stderr, "invalid input")
continue
}
/*将获得的用户输入构建为polar,写入到问题通道*/
questions <- polar{radius, θ}
/*之前建立的goroutine异步线程会根据问题通道的数据进行处理并直接写入回答通道,这里直接从回答通道即可得到转换后的笛卡尔坐标,在没计算出结果前该通道会阻塞*/
coord := <-answers
fmt.Printf(result, radius, θ, coord.x, coord.y)
}
fmt.Println()
}