
Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。
Go 标记
Go 程序可以由多个标记组成,可以是关键字,标识符,常量,字符串,符号。如以下 GO 语句由 6 个标记组成:
fmt.Println("Hello, World!")
6 个标记是(每行一个):
fmt
.
Println
(
"Hello, World!"
)
行分隔符
在 Go 程序中,一行代表一个语句结束。每个语句不需要像 C 家族中的其它语言一样以分号 ; 结尾,因为这些工作都将由 Go 编译器自动完成。
如果你打算将多个语句写在同一行,它们则必须使用 ; 人为区分,但在实际开发中我们并不鼓励这种做法。
以下为两个语句:
fmt.Println("Hello, World!")
fmt.Println("菜鸟教程:runoob.com")
注释
注释不会被编译,每一个包应该有相关注释。
单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾。如:
// 单行注释/*
Author by 菜鸟教程
我是多行注释
*/
标识符
标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母(A~Z和a~z)数字(0~9)、下划线_组成的序列,但是第一个字符必须是字母或下划线而不能是数字。
以下是有效的标识符:
mahesh kumar abc move_name a_123
myname50 _temp j a23b9 retVal
以下是无效的标识符:
1ab(以数字开头)
case(Go 语言的关键字)
a+b(运算符是不允许的)
字符串连接
Go 语言的字符串可以通过 + 实现:
实例
package main
import "fmt"
func main() {
fmt.Println("Google" + "Runoob")
}
以上实例输出结果为:
GoogleRunoob
启动一个 Go 协程
调用函数或者方法时,在前面加上关键字 go,可以让一个新的 Go 协程并发地运行
package mainimport (
"fmt")func hello() {
fmt.Println("Hello world goroutine")}func main() {
go hello()
fmt.Println("main function")}解释代码:go hello() 启动了一个新的 Go 协程。现在 hello() 函数与 main() 函数会并发地执行。
主函数会运行在一个特有的 Go 协程上,它称为 Go 主协程(Main Goroutine)。
执行上边代码,你会发现程序只是打印出了main function 而未打印hello函数中的内容。这是因为:
启动一个新的协程时,协程的调用会立即返回。与函数不同,程序控制不会去等待 Go 协程执行完毕。在调用 Go 协程之后,程序控制会立即返回到代码的下一行,忽略该协程的任何返回值。
如果希望运行其他 Go 协程,Go 主协程必须继续运行着。如果 Go 主协程终止,则程序终止,于是其他 Go 协程也不会继续运行。
增加一行代码延迟结束主协程:
time.Sleep(1 * time.Second)这只是用于测试可以这样写,事实上后边我们会使用信道解决这个问题。
为了更好地理解 Go 协程,我们再编写一个程序,启动多个 Go 协程。
package mainimport (
"fmt"
"time")func numbers() {
for i := 1; i <= 5; i++ {
time.Sleep(250 * time.Millisecond)
fmt.Printf("%d ", i)
}}func alphabets() {
for i := 'a'; i <= 'e'; i++ {
time.Sleep(400 * time.Millisecond)
fmt.Printf("%c ", i)
}}func main() {
go numbers()
go alphabets()
time.Sleep(3000 * time.Millisecond)
fmt.Println("main terminated")}解释代码:
启动了两个 Go 协程。现在,这两个协程并发地运行。
numbers 协程首先休眠 250 微秒,接着打印 1,然后再次休眠,打印 2,依此类推,一直到打印 5 结束。
alphabete 协程同样打印从 a 到 e 的字母,并且每次有 400 微秒的休眠时间。
Go 主协程启动了 numbers 和 alphabete 两个 Go 协程,休眠了 3000 微秒后终止程序。
信道
信道:信道可以想像成 Go 协程之间通信的管道。如同管道中的水会从一端流到另一端,通过使用信道,数据也可以从一端发送,在另一端接收。
信道声明:所有信道都关联了一个类型。信道只能运输这种类型的数据,而运输其他类型的数据都是非法的。
chan T 表示 T类型的信道。
信道的零值为 nil
信道的零值没有什么用,应该像对 map 和切片所做的那样,用 make 来定义信道。
package mainimport "fmt"func main() {
var a chan int
if a == nil {
fmt.Println("channel a is nil, going to define it")
a = make(chan int)
fmt.Printf("Type of a is %T", a)
}}简短声明通常也是一种定义信道的简洁有效的方法:
a := make(chan int)通过信道进行发送和接收
data := <- a // 读取信道
a a <- data // 写入信道 a在第一行,箭头对于 a 来说是向外指的,因此我们读取了信道 a 的值,并把该值存储到变量 data。
在第二行,箭头指向了 a,因此我们在把数据写入信道 a。
发送与接收默认是阻塞的
当把数据发送到信道时,程序控制会在发送数据的语句处发生阻塞,直到有其它 Go 协程从信道读取到数据,才会解除阻塞。
当读取信道的数据时,如果没有其它的协程把数据写入到这个信道,那么读取过程就会一直阻塞着。
信道的这种特性能够帮助 Go 协程之间进行高效的通信,不需要用到其他编程语言常见的显式锁或条件变量。
代码示例:
package mainimport (
"fmt")func hello(done chan bool) {
fmt.Println("Hello world goroutine")
done <- true}func main() {
done := make(chan bool)
go hello(done)
<-done
fmt.Println("main function")}解释代码:
创建了一个 bool 类型的信道 done,并把 done 作为参数传递给了 hello 协程
<-done 这行代码通过信道 done 接收数据,但并没有使用数据或者把数据存储到变量中。这完全是合法的。我们通过信道 done 接收数据。这一行代码发生了阻塞,除非有协程向 done 写入数据,否则程序不会跳到下一行代码。
现在我们的 Go 主协程发生了阻塞,等待信道 done 发送的数据。
写个demo示例,需求:定义一个整数,该程序会计算一个数中每一位的平方和与立方和,然后把平方和与立方和相加并打印出来。
构建程序:
一个单独的 Go 协程计算平方和
一个协程计算立方和,
在 Go 主协程把平方和与立方和相加。
package mainimport ("fmt")
func calcSquares(number int, squareop chan int) {
sum := 0
for number != 0 {
digit := number % 10
sum += digit * digit
number /= 10
}
squareop <- sum}func calcCubes(number int, cubeop chan int) {
sum := 0
for number != 0 {
digit := number % 10
sum += digit * digit * digit
number /= 10
}
cubeop <- sum} func main() {
number := 589
sqrch := make(chan int)
cubech := make(chan int)
go calcSquares(number, sqrch)
go calcCubes(number, cubech)
squares, cubes := <-sqrch, <-cubech
fmt.Println("Final output", squares + cubes)}死锁
当 Go 协程给一个信道发送数据时,照理说会有其他 Go 协程来接收数据。如果没有的话,程序就会在运行时触发 panic,形成死锁。同样的反之亦然。
package mainfunc main() {
ch := make(chan int)
ch <- 5}这段代码就会触发 panic :
fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan send]: main.main()
/tmp/sandbox249677995/main.go:6 +0x80单向信道
之前介绍的信道都是双向信道,即通过信道既能发送数据,又能接收数据。
其实也可以创建单向信道,这种信道只能发送或者接收数据。
代码:
package mainimport "fmt"
func sendData(sendch chan<- int) {
sendch <- 10}func main() {
sendch := make(chan<- int)
go sendData(sendch)
fmt.Println(<-sendch) }创建了唯送(Send Only)信道 sendch。chan<- int 定义了唯送信道,因为箭头指向了 chan。 fmt.Println(<-sendch) 编译器会报错。
信道转换(Channel Conversion)
把一个双向信道转换成唯送信道或者唯收(Receive Only)信道都是行得通的,但是反过来就不行。
package mainimport "fmt"func sendData(sendch chan<- int) {
sendch <- 10}func main() {
cha1 := make(chan int)
go sendData(cha1)
fmt.Println(<-cha1)}解释代码:
函数 sendData 里的参数 sendch chan<- int把 cha1 转换为一个唯送信道。于是该信道在 sendData 协程里是一个唯送信道,而在 Go 主协程里是一个双向信道。该程序最终打印输出 10。
关闭信道和使用 for range 遍历信道
数据发送方可以关闭信道,通知接收方这个信道不再有数据发送过来。
当从信道接收数据时,接收方可以多用一个变量来检查信道是否已经关闭。
v, ok := <- chpackage mainimport ("fmt")
func producer(chnl chan int) {
for i := 0; i < 10; i++ {
chnl <- i }
close(chnl)}func main() {
ch := make(chan int)
go producer(ch)
for {
v, ok := <-ch if ok == false {
break
}
fmt.Println("Received ", v, ok)
}}producer 协程会从 0 到 9 写入信道 chn1,然后关闭该信道。主函数有一个无限的 for 循环(第 16 行),使用变量 ok(第 18 行)检查信道是否已经关闭。如果 ok 等于 false,说明信道已经关闭,于是退出 for 循环。如果 ok 等于 true,会打印出接收到的值和 ok 的值。
for range 循环用于在一个信道关闭之前,从信道接收数据。
package mainimport (
"fmt")func producer(chnl chan int) {
for i := 0; i < 10; i++ {
chnl <- i }
close(chnl)}func main() {
ch := make(chan int)
go producer(ch)
for v := range ch {
fmt.Println("Received ",v)
}}package mainimport (
"fmt")func digits(number int, dchnl chan int) {
for number != 0 {
digit := number % 10
dchnl <- digit
number /= 10
}
close(dchnl)}func calcSquares(number int, squareop chan int) {
sum := 0
dch := make(chan int)
go digits(number, dch)
for digit := range dch {
sum += digit * digit }
squareop <- sum}func calcCubes(number int, cubeop chan int) {
sum := 0
dch := make(chan int)
go digits(number, dch)
for digit := range dch {
sum += digit * digit * digit }
cubeop <- sum}func main() {
number := 589
sqrch := make(chan int)
cubech := make(chan int)
go calcSquares(number, sqrch)
go calcCubes(number, cubech)
squares, cubes := <-sqrch, <-cubech
fmt.Println("Final output", squares+cubes)