Go learn essential language Case (3)

21. coroutine

Go on a coroutine execution is lightweight threads.

package main

import (
	"fmt"
)
func f(from string) {
	for i := 0; i < 3; i++ {
		fmt.Println(from, ":", i)
	}
}
func main() {
	//假设我们有一个函数叫做 f(s)。我们使用一般的方式调并同时运行。
	f("direct")

	//使用 go f(s) 在一个 Go 协程中调用这个函数。
	// 这个新的 Go 协程将会并行的执行这个函数调用。
	go f("goroutine")

	//你也可以为匿名函数启动一个 Go 协程。
	go func(msg string) {
		fmt.Println(msg)
	}("going")

	//现在这两个 Go 协程在独立的 Go 协程中异步的运行,所以我们需要等它们执行结束。
	// 这里的 Scanln 代码需要我们在程序退出前按下任意键结束。
	var input string
	fmt.Scanln(&input)
	fmt.Println("done")
}

Execution results as shown below:
Here Insert Picture Description

When we run this program, you will first see the output blocking calls and then alternately output two Go coroutine. This alternative case is represented when the Go run asynchronously coroutine.

22. Channel

Channel is a plurality of pipe connections Go coroutine. Go You can coroutine from one value to the channel, and then receives another coroutine Go.

package main

import (
	"fmt"
)

func main() {
	//使用 make(chan val-type) 创建一个新的通道。
	//通道类型就是他们需要传递值的类型。
	messages := make(chan string)

	//使用 channel <- 语法 发送 一个新的值到通道中。
	// 这里我们在一个新的 Go 协程中发送 "ping" 到上面创建的messages 通道中。
	go func() { messages <- "ping" }()

	//使用 <-channel 语法从通道中 接收 一个值。
	// 这里将接收我们在上面发送的 "ping" 消息并打印出来。
	msg := <-messages
	fmt.Println(msg)
}

Execution results as shown below:
Here Insert Picture Description
We run the program, through the channel, the message "ping" successfully passed from one another coroutine Go. The default transmit and receive operations are blocked until the sender and the receiver are ready. This feature allows us, do not use any other synchronization to wait for the message "ping" at the end of the program.

23. The channel buffer

The default channels are unbuffered, which means that only the corresponding received - when (<chan) ready to receive the channel only allowed to transmit (chan <-). Cacheable allow passage without a corresponding recipient, a defined number of cache values.

package main

import (
	"fmt"
)

func main() {
	//这里我们 make 了一个通道,最多允许缓存 2 个值。
	messages := make(chan string, 2)
	
	//因为这个通道是有缓冲区的,即使没有一个对应的并发接收方,我们仍然可以发送这些值。
	messages <- "buffered"
	messages <- "channel"
	//然后我们可以像前面一样接收这两个值。

	fmt.Println(<-messages)
	fmt.Println(<-messages)
}

Execution results as shown below:
Here Insert Picture Description

24. Synchronization Channel

We can use the channel to synchronize execution state between Go coroutine. Here is the end of a way to accept the use of blocking Go wait for a co-operation process.

package main

import (
	"fmt"
	"time"
)
//这是一个我们将要在 Go 协程中运行的函数。
// done 通道将被用于通知其他 Go 协程这个函数已经工作完毕。
func worker(done chan bool) {
	fmt.Print("working...")
	time.Sleep(time.Second)
	fmt.Println("done")

	//发送一个值来通知我们已经完工啦。
	done <- true
}

func main() {
	//运行一个 worker Go协程,并给予用于通知的通道。
	done := make(chan bool, 1)
	go worker(done)
	
	//程序将在接收到通道中 worker 发出的通知前一直阻塞。
	<-done
}

Execution results as shown below:
Here Insert Picture Description

If you <- done this line of code is removed from the program, the program even when the worker has not yet begun to run over.

25. The channel direction

When a channel as a parameter, you can specify that the channel is not only used to transmit or receive value. This feature improves type safety program.

package main

import (
	"fmt"
)
//ping 函数定义了一个只允许发送数据的通道。
// 尝试使用这个通道来接收数据将会得到一个编译时错误。
func ping(pings chan<- string, msg string) {
	pings <- msg
}

//pong 函数允许通道(pings)来接收数据,另一通道(pongs)来发送数据。
func pong(pings <-chan string, pongs chan<- string) {
	msg := <-pings
	pongs <- msg
}

func main() {
	pings := make(chan string, 1)
	pongs := make(chan string, 1)
	ping(pings, "passed message")
	pong(pings, pongs)
	fmt.Println(<-pongs)
}

Execution results as shown below:
Here Insert Picture Description

26. The channel selector

Go the channel selector so you can wait for multiple channel operation at the same time. Go coroutine binding and channel selector and is a powerful feature of Go.

package main

import (
	"fmt"
	"time"
)

func main() {
	//例子中,我们将从两个通道中选择。
	c1 := make(chan string)
	c2 := make(chan string)

	//各个通道将在若干时间后接收一个值,这个用来模拟例如并行的 Go 协程中阻塞的 RPC 操作
	go func() {
		time.Sleep(time.Second * 1)
		c1 <- "one"
	}()
	go func() {
		time.Sleep(time.Second * 2)
		c2 <- "two"
	}()
	//我们使用 select 关键字来同时等待这两个值,并打印各自接收到的值。
	for i := 0; i < 2; i++ {
		select {
		case msg1 := <-c1:
			fmt.Println("received", msg1)
		case msg2 := <-c2:
			fmt.Println("received", msg2)
		}
	}
}

Execution results as shown below:
Here Insert Picture Description
Note that the first and second Sleeps concurrent execution, running total only about two seconds.

27. Timeout Handling

Timeout for a connection to an external resource, or some other program in terms of the time it takes to perform the operation is very important. Thanks to the channel and select, implement a timeout operation in Go is simple and elegant.

package main

import (
	"fmt"
	"time"
)

func main() {
	//在此例子中,假如我们执行一个外部调用,并在 2 秒后通过通道 c1 返回它的执行结果。
	c1 := make(chan string, 1)
	go func() {
		time.Sleep(time.Second * 2)
		c1 <- "result 1"
	}()

	//这里是使用 select 实现一个超时操作。
	//res := <- c1 等待结果,<-Time.After 等待超时时间 1 秒后发送的值。
	//由于select默认处理第一个已准备好的接收操作,如果这个操作超过了允许的1秒的话,将会执行超时case。
	select {
	case res := <-c1:
		fmt.Println(res)
	case <-time.After(time.Second * 1):
		fmt.Println("timeout 1")
	}

	//如果我允许一个长一点的超时时间 3 秒,将会成功的从 c2接收到值,并且打印出结果。
	c2 := make(chan string, 1)
	go func() {
		time.Sleep(time.Second * 2)
		c2 <- "result 2"
	}()
	select {
	case res := <-c2:
		fmt.Println(res)
	case <-time.After(time.Second * 3):
		fmt.Println("timeout 2")
	}
}

Execution results as shown below:
Here Insert Picture Description

Run the program, first time out of the show running operation, and then successfully received. Use this timeout mode select, the results need to use the transmission channel. This is a good way for the general case, because the other important characteristic Go is based on and select the channel. Then look at two examples: timer and ticker.

28. The nonblocking channel operation

Conventional send and receive data through the channel is blocked. However, we can use the default clause of a select band to achieve non-blocking send, receive, or even non-blocking multi-channel select.

package main

import (
	"fmt"
)

func main() {
	messages := make(chan string)
	signals := make(chan bool)

	//这里是一个非阻塞接收的例子。
	// 如果在 messages 中存在,然后 select 将这个值带入 <-messages case中。
	// 如果不是,就直接到 default 分支中。
	select {
	case msg := <-messages:
		fmt.Println("received message", msg)
	default:
		fmt.Println("no message received")
	}

	//一个非阻塞发送的实现方法和上面一样。
	msg := "hi"
	select {
	case messages <- msg:
		fmt.Println("sent message", msg)
	default:
		fmt.Println("no message sent")
	}
	
	//我们可以在 default 前使用多个 case 子句来实现一个多路的非阻塞的选择器。
	// 这里我们试图在 messages和 signals 上同时使用非阻塞的接受操作。
	select {
	case msg := <-messages:
		fmt.Println("received message", msg)
	case sig := <-signals:
		fmt.Println("received signal", sig)
	default:
		fmt.Println("no activity")
	}
}

Execution results as shown below:
Here Insert Picture Description

29. The closed channel

Closing a channel means that this channel can no longer send a value. This feature can be used to convey the work to the receiving party of this channel has been completed.

package main

import (
	"fmt"
)

func main() {
	//在这个例子中,我们将使用一个 jobs 通道来传递 main() 中 Go协程任务执行的结束信息到一个工作 Go 协程中。
	//当我们没有多余的任务给这个工作 Go 协程时,我们将 close 这个 jobs 通道。
	jobs := make(chan int, 5)
	done := make(chan bool)

	//这是工作 Go 协程。使用 j, more := <- jobs 循环的从jobs 接收数据。
	// 在接收的这个特殊的二值形式的值中,如果 jobs 已经关闭了,并且通道中所有的值都已经接收完毕,
	// 那么 more 的值将是 false。当我们完成所有的任务时,将使用这个特性通过 done 通道去进行通知。
	go func() {
		for {
			j, more := <-jobs
			if more {
				fmt.Println("received job", j)
			} else {
				fmt.Println("received all jobs")
				done <- true
				return
			}
		}
	}()

	//这里使用 jobs 发送 3 个任务到工作函数中,然后关闭 jobs。
	for j := 1; j <= 3; j++ {
		jobs <- j
		fmt.Println("sent job", j)
	}
	close(jobs)
	fmt.Println("sent all jobs")

	//我们使用前面学到的通道同步方法等待任务结束。
	<-done
}

Execution results as shown below:
Here Insert Picture Description

30. Channel traversal

In the previous example, we talked about for range and provides a functional iteration as the basic data structure. We can also use this syntax to traverse the value obtained from the channel.

package main

import (
	"fmt"
)

func main() {
	//遍历在 queue 通道中的两个值。
	queue := make(chan string, 2)
	queue <- "one"
	queue <- "two"
	close(queue)

	//这个 range 迭代从 queue 中得到的每个值。
	// 因为我们在前面 close 了这个通道,这个迭代会在接收完 2 个值之后结束。
	// 如果我们没有 close 它,我们将在这个循环中继续阻塞执行,等待接收第三个值
	for elem := range queue {
		fmt.Println(elem)
	}
}

The results as shown below:
Here Insert Picture Description
This example also allows us to see, is a non-empty channel can be closed, but the rest of the channel value can still be received.

Previous: Learn the essential language Go Case (2)

Next: Learn the essential language Go Case (4)

Guess you like

Origin blog.csdn.net/cui_yonghua/article/details/91575648