Golang Goroutine 和 Channel 的使用

参考阅读:

Goroutine
https://golangbot.com/goroutines/
Channel
https://golangbot.com/channels/

什么是 Goroutine

Goroutines 是与其他函数或方法同时运行的函数或方法。Goroutines可以被认为是轻量级线程。 与线程相比,创建Goroutine的成本很小。因此,Go应用程序通常会同时运行数千个Goroutines。

Goroutine 的优势

1)与线程相比,Goroutines非常便宜。 它们的堆栈大小只有几kb,堆栈可以根据应用程序的需要增长和缩小,而在线程的情况下,堆栈大小必须指定并且是固定的。
2)Goroutines被多路复用到较少数量的OS线程。程序中可能只有一个线程有数千个Goroutines。 如果该线程中的任何Goroutine阻止等待用户输入,则创建另一个OS线程,并将剩余的Goroutines移动到新的OS线程。 所有这些都由运行时处理,我们作为程序员从这些复杂的细节中抽象出来,并给出一个干净的API来处理并发。
3)Goroutines使用Channel进行交流。设计Channel可防止使用Goroutines访问共享内存时发生竞争条件。Channel可以被认为是Goroutines通信的管道。

如何创建一个 Goroutine

package main

import (
“fmt”
)

func hello() {
fmt.Println(“Hello world goroutine”)
}
func main() {
go hello()
fmt.Println(“main function”)
}

Goroutine 两个主要属性

1)当一个新的Goroutine启动时,goroutine呼叫立即返回。 与函数不同,控件不会等待Goroutine完成执行。 在Goroutine调用之后,控件立即返回到下一行代码,并忽略Goroutine的任何返回值。
2)主要的Goroutine应该运行任何其他Goroutines运行。 如果主要的Goroutine终止,则该程序将被终止,并且没有其他Goroutine将运行。

package main

import (
“fmt”
“time”
)

func hello() {
fmt.Println(“Hello world goroutine”)
}
func main() {
go hello()
time.Sleep(1 * time.Second)
fmt.Println(“main function”)
}

通过 time.sleep 可以模拟等待其他routine执行结束再退出的情形,实际情况下我们可以使用channel。channel 可用于阻挡主Goroutine,直到所有其他Goroutines完成执行。

Goroutine 的通讯管道 Channel

通道可以被认为是Goroutines通信的管道。类似于水在管道中从一端流向另一端的方式,数据可以从一端发送,另一端使用通道接收。

如何定义Channel
每个通道都有一个与之关联的类型。此类型是允许通道传输的数据类型。不允许使用该频道传输其他类型。
Channel的零值为nil。 nil通道没有任何用处,因此必须使用类似于map和slice的make来定义通道。

package main

import “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)

既然Channel可以从一端写入数据,从另一端读取数据,具体是如何做到呢?

data := <- a // read from channel a
a <- data // write to channel a

注意点:

默认情况下,对通道的发送和接收是阻止的。 这是什么意思? 当数据发送到通道时,控制在发送语句中被阻止,直到其他Goroutine从该通道读取。 类似地,当从通道读取数据时,读取被阻止,直到一些Goroutine将数据写入该通道。

通道的这种属性有助于Goroutines有效地进行通信,而无需使用在其他编程语言中非常常见的显式锁或条件变量。

Sends and receives to a channel are blocking by default. What does this mean? When a data is sent to a channel, the control is blocked in the send statement until some other Goroutine reads from that channel. Similarly when data is read from a channel, the read is blocked until some Goroutine writes data to that channel.

This property of channels is what helps Goroutines communicate effectively without the use of explicit locks or conditional variables that are quite common in other programming languages.

来一个示例来看看

package main

import (
“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”)
}

这里主函数main中创建了一个类型为bool的channel,在启动一个goroutine后,立即从done中读取数据,这样就会阻塞main函数流程,直到done中有数据写入,即hello函数的执行,当done收到数据后,main函数的控制流程才会继续往下面走。为了更加详细地理解这个过程,请看如下示例:

package main

import (
“fmt”
“time”
)

func hello(done chan bool) {
fmt.Println(“hello go routine is going to sleep”)
time.Sleep(4 * time.Second)
fmt.Println(“hello go routine awake and going to write to done”)
done <- true
}
func main() {
done := make(chan bool)
fmt.Println(“Main going to call hello go goroutine”)
go hello(done)
<-done
fmt.Println(“Main received data”)
}

输出内容如下:

Main going to call hello go goroutine
hello go routine is going to sleep
hello go routine awake and going to write to done
Main received data

从输出结果可以得出结论,<-done 确实在hello sleep 4 秒的时候在等待,直到获取到管道中的数据。

Channel 死锁

使用Channel时要考虑的一个重要因素是死锁。 如果Goroutine正在通道上发送数据,那么预计其他一些Goroutine应该接收数据。 如果没有发生这种情况,程序将在运行时因死锁而发生Panic。

同样,如果Goroutine正在等待从一个Channel接收数据,那么其他一些Goroutine预计会在该Channel上写入数据,否则程序将会出现Panic。如下这种情况就会Panic

package main

func main() {
ch := make(chan int)
ch <- 5
}

单向Channel

到目前为止我们讨论的所有Channel都是双向的,即数据可以在它们上发送和接收。 也可以创建单向Channel,即仅发送或接收数据的Channel。如下是一个单向写入,但不允许读取的Channel

package main

import “fmt”

func sendData(sendch chan<- int) {
sendch <- 10
}

func main() {
sendch := make(chan<- int)
go sendData(sendch)
fmt.Println(<-sendch)
}

运行输出如下:

invalid operation: <-sendch (receive from send-only type chan<- int)

这个例子还告诉我们,写一个只能写入却不能读取的Channel没什么意义。

这是Channel转换投入使用的地方。可以将双向Channel转换为仅发送或仅接收Channel,反之亦然。如下示例:

package main

import “fmt”

func sendData(sendch chan<- int) {
sendch <- 10
}

func main() {
sendch := make(chan int)
go sendData(sendch)
fmt.Println(<-sendch)
}

输出: 10

关闭Channel 和 Channel 在循环体中的轮询

Channel 的数据发送者能够关闭Channel以通知接收者不再在该Channel上发送数据。Receivers可以在从Channel接收数据时使用附加变量来检查通道是否已关闭。

v, ok := <- ch

在上面的语句中,如果通过成功的发送操作接收到该值,则确定为真。 如果ok为false,则意味着我们正在从封闭的Channel中读取。 从封闭Channel读取的值将是通道类型的零值。 例如,如果通道是int通道,则从闭合Channel接收的值将为0。

package main

import (
“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)
}
}

这个示例比较简单,从product方法中写入channel结束后,直接关闭channel,由于main中的for循环体中不停地读取,当收到close信号时结束读取。

值得注意的是这里的循环结束条件,通过获取Channel的close信号,那么是否有更简洁的写法呢?

package main

import (
“fmt”
)

func producer(chnl chan int) {
for i := 0; i < 10; i++ {
fmt.Println("Inpute ", i)
chnl <- i
}
fmt.Println(“Channel will Close.”)
close(chnl)
}
func main() {
ch := make(chan int)
go producer(ch)
for v := range ch {
fmt.Println("Received ",v)
}
}

输出:

Inpute 0
Inpute 1
Received 0
Received 1
Inpute 2
Inpute 3
Received 2
Received 3
Inpute 4
Inpute 5
Received 4
Received 5
Inpute 6
Inpute 7
Received 6
Received 7
Inpute 8
Inpute 9
Received 8
Received 9
Channel will Close.

for range 中在从Channel中读取数据,但Channel关闭的时候,循环自动就退出了。这里我们可以节省判断Channel是否被关闭的信号。

你可能感兴趣的:(Golang)