o 语言有一个很重要的特性就是 goroutine, 咱们可使用 goroutine 结合 channel 来开发并发程序。缓存
并发程序指的是能够同时运行多个任务的程序,这里的同时运行并不必定指的是同一时刻执行,在单核CPU的机器下,在同一时刻只可能有一个任务在执行,可是因为CPU的速度很快,在不断的切换着多个任务,让它们交替的执行,所以宏观上看起来就像是同时在运行; 而在多核的机器上,并发程序中的多个任务是能够实如今同一时刻执行多个的,此时并发的多个任务是在并行执行的。安全
goroutine 是 go 语言中的并发执行单元,咱们能够将多个任务分别放在多个 goroutine 中,来实现并发程序。下面先看一个例子:数据结构
package main import "fmt" func hello() { fmt.Println("Hello World!!!") } func main() { go hello() fmt.Println("Bye!!!") var input string fmt.Scanln(&input) }
上述程序的执行结果以下:并发
Bye!!! Hello World!!!
上面这个例子展现了使用 goroutine 的几个要点:函数
go hello()
就是用于建立一个 goroutine, 即 go 关键字加上 要在 goroutine 中执行的函数(也能够是匿名函数,不过必须是调用的形式)以上就是 goroutine 的基本用法学习
前面咱们学习了怎样建立并行的执行单元,可是每一个执行单元之间是彻底独立的,若是咱们想在运行期间交换数据,即进行通讯,此时就得依靠另外一个概念 - channels, 即通道,这个名字十分贴切,就像在不一样的并发执行单元之间链接了一根管道,而后经过这跟管道来发送和接收数据。code
goroutine 和 channel 常常结合在一块儿使用,下面学习一些 channel 的用法:开发
建立 channel字符串
ch1 := make(chan int)
channel 也须要使用 make 函数来建立,也就是说 channel 也是一种引用类型(make函数会返回低层数据结构的引用给channel)input
向 channel 中读写数据
前面说了 channel 是用于 goroutine 之间通讯的, 天然可以从 channel 中写入和读取数据,使用的都是 <-
操做符
ch := make(chan int) ch<- 1 // 向 channel 中写入数据 var a int = <-ch // 从 channel 中读取数据
关闭 channel
在咱们使用完一个 channel 以后,能够调用 close() 方法来关闭一个 channel, 关闭以后的通道,不可以再进行数据的写操做, 可是仍然能够读取以前写入成功的数据(若是没有数据了,将返回零值)。
channel 的基本操做就是上面这么多,不过实际上,channel 是有两种的: 无缓冲的 和 有缓冲的。上面咱们建立的是无缓存的,有缓存的建立方式是 ch := make(chan int, 2)
, 两者的区别是:
例1: 通道用于传递消息
package main import "fmt" func main() { message := make(chan string) // 建立一个用于传递字符串的通道 go func() { message <- "This is a message." // 向 channel 写入数据 }() msg := <- message // 从 channel 读取数据 fmt.Println(msg) }
例2: 利用通道进行同步
package main import "fmt" func hello() { fmt.Println("Hello World!!!") done <- true } func main() { done := make(chan bool) go hello() fmt.Println("Bye!!!") <-done // 这里会阻塞住,直到在另外一个 goroutine 中对 done 进行写入操做以后 }
当使用 channel 做为参数,咱们能够指定 channel 为单向的,即让通道在函数中只能发送,或者只能接收数据,以此来提升程序的安全性.
语法:
<-chan type
表示一个只能接收数据的通道chan<- type
表示一个只能发送数据的通道例子:
package main import "fmt" // 这里的 message 在函数 send 中就是一个只能发送数据的通道 func send(msg string, message chan<- string) { message<- msg } // 这里的 message 在函数 receive 中就是一个只能发送数据的通道 func receive(message <-chan string) string { msg := <- message return msg } func main() { message := make(chan string) go send("hello", message) fmt.Println(receive(message)) }
输出结果是 hello
, 此时在函数 send 中,message 通道就只能用于发送数据,而在函数 receive 中通道只能接收数据,经过参数的限制使其在函数内部成为了单向的通道。
go语言提供了一个 select 关键字,可使用它来等待多个通道的操做,以实现多路复用。语法:
select { case <-ch1: ... case ch2 <- value: ... default: ... }
其中的每一个 case 表示一个 channel 的操做,当case语句后面指定通道的操做能够执行时,select 才会执行 case 以后的语句。此时其余的语句都不会被执行。
例子: 超时处理
package main import "time" import "fmt" func main() { ch1 := make(chan string, 1) go func() { time.Sleep(time.Second * 2) ch1 <- "result 1" }() select { case res := <- ch1: fmt.Println(res) case <-time.After(time.Second * 1): fmt.Println("timeout 1") } ch2 := make(chan string, 1) go func() { time.Sleep(time.Second * 2) ch2 <- "result 2" }() select { case res := <-ch2: fmt.Println(res) case <-time.After(time.Second * 3): fmt.Println("timeout 2") } }
上面的例子中咱们定义了两个通道和两个select结构,是为了进行对比,第一个channel会在等待两秒以后被写入数据,而在 select 中,第二个case语句只会等待一秒,而后就会执行,所以就会执行超时操做。而在第二个 select 中,第二个 case 语句会等待三秒。因此上述程序的结果以下:
timeout 1 result 2