Go语言-Goroutine和通道
goroutine
- 顺序执行 :一个任务完成之后再执行下一个
- 并行: 不必等到一个操作执行完毕后再执行下一个
并发和并行
并发就是同时处理很多事情,而并行就是同时做很多事情。
- 并行:指同一时刻同时进行,进程并行需要多个处理器支持。
- 并发:指一段时间内的多个进程轮流切换使CPU。
模拟阻塞执行
// Filename: go.org[*Org Src go.org[ go ]*]
// coding:utf-8
// 模拟阻塞的代码
package main
import (
"fmt"
"time"
)
func main () {
slowFunc()
fmt.Println("I'm not shown until slowFunc() completes.")
}
//
func slowFunc () {
fmt.Println("Sleepper() start")
time.Sleep(time.Second)
fmt.Println("Sleepper() finished")
}
goroutine 使用: 只需要让Goroutine执行的函数或方法前加上关键字go即可
使用 go关键字
// Filename: go.org[*Org Src go.org[ go ]*]
// coding:utf-8
// 使用goroutine
package main
import (
"fmt"
"time"
)
func main () {
go slowFunc()
fmt.Println("I'm not shown straightaway!")
}
//
func slowFunc () {
fmt.Println("Sleeper() started")
time.Sleep(time.Second)
fmt.Println("Sleeper() finished")
}
程序输出一行就直接退出了,因为goroutine 立即返回,程序直接执行后面的代码,然后退出,如果没有其他因素阻止,程序将在 goroutine 返回前退出。
阻止程序直接退出
// Filename: go.org[*Org Src go.org[ go ]*]
// coding:utf-8
// 使用goroutine
import (
"fmt"
"time"
)
func main () {
go slowFunc()
fmt.Println("I'm not shown straightaway!")
time.Sleep(time.Second * 2)
}
//
func slowFunc () {
fmt.Println("Sleeper() started")
time.Sleep(time.Second)
fmt.Println("Sleeper() finished")
}
Goroutine
- go 在幕后使用线程来管理并发,但Goroutine 让程序员无需直接管理线程。
- 创建一个Goroutine 只需要占用几KB内存,因此即便创建数千个 Goroutine 也不会耗尽内存。
-
另外创建和销毁Goroutine 的效率也非常的高。
- 并发地执行代码意味着程序可能更快的执行完毕,并在数据就绪后就返回,而无需等待程序的其他部分结束。
- 阻塞式的代码让程序暂停执行。
- 使用并法编程的情景包括从磁盘文件读取数据,从网络读写数据以及从数据库读写数据。
通道 chan
- 通道和 Goroutine 一起提供了一个受控的环境,通道让 Goroutine 能够互相通信,用于开发并发软件。
如果说 Goroutine 是一种支持并发编程的方式,那么通道就是一种与 Goroutine 通信的方式。 通道让数据能够进入和离开 Goroutine, 可方便 Goroutine 之间的通信。
- 不要通过共享内存来通信,而通过通信来共享内存
- 通道只能有一种数据类型,可以创建任意类型的通道,因此可以使用结构体来存储复杂的数据结构
使用通道进行通信
// Filename: go.org[*Org Src go.org[ go ]*]
// coding:utf-8
// 使用通道进行通信
import (
"fmt"
"time"
)
func main () {
c := make(chan string)
go slowFunc(c)
msg := <-c
fmt.Println(msg)
}
//
func slowFunc (c chan string) {
time.Sleep(time.Second * 2)
c <- "slowFunc() finished"
}
使用缓冲通道
- 缓冲通道只能存储指定数量的消息
如果向它发送更多的消息,将导致错误
- close 用来关闭通道,禁止再向通道发送消息
// Filename: go.org[*Org Src go.org[ go ]*]
// coding:utf-8
//
import (
"fmt"
"time"
)
func main () {
messages := make(chan string, 2)
messages <- "hello"
messages <- "world"
close(messages)
fmt.Println("Pushed two messages onto Channel with no receivers")
time.Sleep(time.Second)
reveiver(messages)
}
//
func reveiver (c chan string) {
for msg := range c {
fmt.Println(msg)
}
}
流程控制
- 给通道指定消息接收者是一个阻塞操作,因为它将阻止函数返回,直到收到一条消息为止。
- 从通道接收并打印消息的程序需要阻塞,以免终止。
通道接收一条消息就返回
// Filename: go.org[*Org Src go.org[ go ]*]
// coding:utf-8
// 通道接收一条消息就返回
import (
"fmt"
"time"
)
func main () {
messages := make(chan string)
go pinger(messages)
msg := <- messages
fmt.Println(msg)
}
//
func pinger (c chan string) {
t := time.NewTicker(1 * time.Second)
for {
c <- "ping"
<- t.C
}
}
创建不断监听通道中消息的监听器
// Filename: go.org[*Org Src go.org[ go ]*]
// coding:utf-8
// 不间断的指定接收操作
import (
"fmt"
"time"
)
func main () {
messages := make(chan string)
go pinger(messages)
for i := 0 ; i < 5 ; i ++ {
msg := <- messages
fmt.Println(msg)
}
}
//
func pinger (c chan string) {
t := time.NewTicker(1 * time.Second)
for {
c <- "ping"
<- t.C
}
}
将通道作为函数参数
- 可以进一步指定在函数中如何使用传入的参数,可在传递通道时指定为只读,只写或读写的。
指定通道访问权限
func channelReader(messages <-chan string){
mes := messages
fmt.Println(msg)
}
// 通道在函数内只写的
func channelWriter (messages chan<- string) {
messages <- "messages"
}
// 没有指定箭头,表示通道可读可写
func channelReaderWriter (messages chan string) {
msg := <- messages
fmt.Println(msg)
messages <- "helloworld"
}
select 语句
结合使用 select 语句和通道
// Filename: go.org[*Org Src go.org[ go ]*]
// coding:utf-8
// select 语句使用
import (
"fmt"
"time"
)
func main () {
channel1 := make(chan string)
channel2 := make(chan string)
go ping1(channel1)
go ping2(channel2)
select {
case msg1 := <- channel1:
fmt.Println("reveived", msg1)
case msg2 := <- channel2:
fmt.Println("reveived", msg2)
}
}
//
func ping1 (c chan string) {
time.Sleep(time.Second)
c <- "ping on channel1"
}
//
func ping2 (c chan string) {
time.Sleep(time.Second * 2)
c <- "ping on channel2"
}
- 哪条消息最先到达,将执行哪条case 语句
- 即根据最先收到的消息采取相应的措施
给 select 语句指明超时时间
// Filename: go.org[*Org Src go.org[ go ]*]
// coding:utf-8
// 给select 语句指定超时时间
import (
"fmt"
"time"
)
func main () {
channel1 := make(chan string)
channel2 := make(chan string)
go ping1(channel1)
go ping2(channel2)
select {
case msg1 := <- channel1:
fmt.Println("reveived", msg1)
case msg2 := <- channel2:
fmt.Println("reveived", msg2)
case <- time.After(500 * time.Millisecond):
fmt.Println("no messages receiverd. giving up")
}
}
//
func ping1 (c chan string) {
time.Sleep(time.Second)
c <- "ping on channel1"
}
//
func ping2 (c chan string) {
time.Sleep(time.Second * 2)
c <- "ping on channel2"
}
退出通道
在已知需要停止的时间的情况下,使用超时时间是不错的选择,在不确定 select 语句该在何时返回, 因此不能使用定时器。 在这种情况下可以使用退出通道。
- 通过在 select 语句中添加一个退出通道,可向退出通道发送消息来结束该语句,从而停止阻塞。
- 关闭缓冲意味着不能再向它发送消息。缓冲的消息会被保留,可供接收者读取。
// Filename: go.org[*Org Src go.org[ go ]*]
// coding:utf-8
// 使用退出通道
import (
"time"
"fmt"
)
func main () {
messages := make(chan string)
stop := make(chan bool)
go sender(messages)
go func() {
time.Sleep(time.Second * 2)
fmt.Println("time is up!")
stop <- true
}()
for {
select {
case <- stop :
return
case msg := <- messages:
fmt.Println(msg)
}
}
}
//
func sender (c chan string) {
t := time.NewTicker(time.Second)
for {
c <- "I'm sending a messages"
<- t.C
}
}
小练习
接收10 条消息后退出程序
// Filename: goroutine_chan.org[*Org Src goroutine_chan.org[ go ]*]
// coding:utf-8
// 通道接收后退出
import (
"fmt"
"time"
)
func main () {
messages := make(chan string)
go sender(messages)
for i := 0; i < 10; i++ {
msg := <- messages
fmt.Println(msg)
}
}
// send messages
func sender (c chan string) {
t := time.NewTicker(time.Second)
for {
c <- "This is a messages."
<- t.C
}
}