概述

Concurrency is a paradigm where different parts of the program can be executed in parallel without impact on the final result. Go programming supports several concurrency concepts related to concurrent execution and communication between concurrent executions.

并发是一种范例,其中程序的不同部分可以并行执行而不会影响最终结果。Go编程支持与并发执行和并发执行之间的通信相关的几个并发概念。

协程

A thread is a small sequence of instructions that can be processed by a single CPU core. Moder hardware architectures have multiple cores, so we can execute multiple threads in paralle.

线程是可以由单个CPU核心处理的一小段指令序列。现代硬件架构有多个内核,因此我们可以并行执行多个线程。

Go programming language offers its own solution for concurrent execution, called goroutines. Goroutines can be defined as lightweight threads (because they are very small, only a couple of KB will be used to store all thread-related data on the stack) managed by Go routine.

Go编程语言为并发执行提供了自己的解决方案,称为gooutines。Go例程可以定义为由Go例程管理的轻量级线程(因为它们非常小,只需要几个KB就可以将所有与线程相关的数据存储在堆栈上)。

If we use the go keyword in front of a function call, that function will be executed in a new goroutine, but the evaluation of arguments will be executed in the current goroutine. Here we have two function calls, the first one will be executed in the current goroutine and for the second one, a new goroutine will be created.

如果我们在函数调用前使用go关键字,那么该函数将在新的运行例程中执行,但参数的计算将在当前运行例程中执行。这里我们有两个函数调用,第一个将在当前的运行例程中执行,第二个将创建新的运行例程。

sendMessage(message1)
go sendMessage(message2)

Goroutines run in the same address space so, in certain situations, goroutines must synchronize memory access and communicate with each other.

运行在相同的地址空间中,因此,在某些情况下,运行例程必须同步内存访问并相互通信。

完整示例代码:

package main

import (
	"fmt"
	"time"
)

func sendMessage(msg string) {
	fmt.Println(msg)
}

func main() {
	sendMessage("hello, Mara!")

	// main goroutine 一旦执行结束,其他goroutine不再执行,被迫结束
	go sendMessage("Hi, Mara!")
	
	// 方法1:延长main的执行时间
	time.Sleep(time.Second)
}

管道

We can define the channel as a construct through which we can send and receive values. In order to send or receive value, we will use the <-(arraow) operator in the following way:

  • ch <- value will send value to channel ch
  • value = <- ch will receive value from channel ch

我们可以将管道定义为可以发送和接收值的构造。为了发送或接收值,我们将以以下方式使用<-(箭头)操作符:

  • ch <- value 将发送值到管道ch
  • value = <- ch 将从管道ch接收值

As can be seen, the data flow is determined by the direction of the arrow.

可以看出,数据流是由箭头方向决定的。

We have two types of channels:

  • Unbuffered without buffer for message storage
  • Buffered with buffer for message storage

我们有两种管道:

  • 不带缓存的管道,没有缓存存储消息
  • 带缓存的管道,通过缓存存储消息

By default, the channel is unbuffered with send and receive as blocking operations. The sender will be blocked until the receiver is ready to pick up the variable from the channel and vice versa. The receiver will be blocked, waiting for the value until the sender sends it to the channel. This can be very useful because there is no need for any additional synchronization.

默认情况下,管道是无缓冲的,发送和接收都是阻塞操作。发送方将被阻塞,直到接收方准备好从通道中获取变量,反之亦然。接收方将被阻塞,等待该值,直到发送方将其发送到通道。这非常有用,因为不需要任何额外的同步。

Traditionally, we use the make() function to create a channel. This will create a new channel that allows us to send and receive string variables.

传统上,我们使用make()函数来创建管道。下面的示例将创建一个允许我们发送和接收字符串变量的新管道。

ch := make(chan string)

Through the channels which we have dealt so far, we can send only one value. But in practice, this is not an acceptable solution, for example, if the sender is faster than the receiver, the sender will be blocked too often. We can avoid that by defining a buffer, now we can accept more variables. Channels with buffers are called buffered channels.

对于我们刚才创建的管道,我们只能发送一个值。但在实践中,这不是一个可接受的解决方案,例如,如果发送方比接收方快,发送方就会经常被阻塞。我们可以通过定义缓冲区来避免这种情况,现在我们可以接受更多的变量。具有缓冲区的通道称为缓冲通道。

A buffered channel will be created by adding buffer size as a second parameter in the make() function.

通过在make()函数中添加缓冲区大小作为第二个参数来创建缓冲通道。

ch = make(chan string, 100)

With the buffered channel, the sender will be blocked only when the buffer is full and the receiver will be blocked only when the buffer is full and the receiver will be blocked only when the buffer is empty. In the next code example, we will use the bufferedchannel to send two messages from new goroutines and receive those messages in the main goroutine.

对于缓冲通道,只有当缓冲区已满时发送方才会被阻塞,只有当缓冲区为空时接收方才会被阻塞。在下一个代码示例中,我们将使用bufferedchannel从新的运行例程发送两条消息,并在主运行例程中接收这些消息。

func sendMessage(message string, ch chan string) {
    ch <- message
}

func main() {
    ch := make(chan string, 2)
    go sendMessage("Hello", ch)
    go sendMessage("World", ch)
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

We cannot influence which goroutine will send the message first, words Hello and World will not always be displayed in that order on standard output.

我们无法影响哪个程序将首先发送消息,单词Hello和World在标准输出中并不总是按该顺序显示。

The sender and only the sender can close the channel when there are no more values to send by calling the close() function.

当没有更多的值可以发送时,只有发送方可以通过调用close()函数关闭通道。

close(ch)

The receiver should check if the channel is closed. In the following expression, variable ok will have the value false when the channel is closed.

接收端应该检查信道是否关闭。在下面的表达式中,当通道关闭时,变量ok的值为false。

v ok <- ch

Constant checking an be tiresome, but luckily, a special kind of for range loop can be used. If we put the channel in for range, the loop will receive values until the channel is closed.

不断的检查是令人厌烦的,但幸运的是,一种特殊的范围循环可以使用。如果我们将通道设置为for range,循环将接收值,直到通道关闭。

for v := range ch {
    fmt.Println(v)
}

If we try to send a message to a closed channel, panic will be triggered. When panic occurs, a message will be displayed on standard output and the function where panic has occurred with crash.

如果我们试图向封闭通道发送信息,就会引发panic异常。当panic发生时,将在标准输出和发生panic的函数上显示一条消息并崩溃。

In all of our previous examples, the receiver waits on only one channel, but Go provides us with the concept that allows the receiver to wait on multiple channels: select statement. Syntatically, select statement is similar to be switch statement, with one differece, keyword select is used instead of keyword switch. The receiver will be blocked until one of the case statements can be executed.

在前面的所有示例中,接收方只在一个通道上等待,但是Go为我们提供了select语句,允许接收方在多个通道上等待。从语法上讲,select语句与switch语句类似,区别在于使用关键字select而不是关键字switch。接收方将被阻塞,直到其中一个case语句可以执行。

select{
    case <- ch1:
        fmt.PPrintln("Channel One")
    case <- ch2:
        fmt.Println("Channel Two")
    default:
        fmt.Println("Waiting")
}

A default case will be executed if no other case isready.

如果没有准备好其他情况,将执行默认情况。

Logo

一起探索未来云端世界的核心,云原生技术专区带您领略创新、高效和可扩展的云计算解决方案,引领您在数字化时代的成功之路。

更多推荐