Go语言开发-过程式编程-通信和并发语句

栏目: Go · 发布时间: 5年前

内容简介:Go语言的通信和并发功能将在第7章详细讲解,但是这里为了过程式编程的完整性,我们会简要介绍其基本语法。goroutine是程序中与其它goroutine相互独立而又并发执行的函数或方法调用。每个Go程序至少有一个goroutine,主goroutine 在main包中的main()函数中运行。goroutine有点像轻量级的线程或协同程序且可以被大量创建(然而对于线程来说,即使数量很少也会消耗大量的机器资源)。所有的goroutine都共享同一块地址空间,Go语言也提供了锁原语来允许在goroutine之间

5.4. 通信和并发语句

Go语言的通信和并发功能将在第7章详细讲解,但是这里为了过程式编程的完整性,我们会简要介绍其基本语法。

goroutine是程序中与其它goroutine相互独立而又并发执行的函数或方法调用。每个 Go 程序至少有一个goroutine,主goroutine 在main包中的main()函数中运行。goroutine有点像轻量级的线程或协同程序且可以被大量创建(然而对于线程来说,即使数量很少也会消耗大量的机器资源)。所有的goroutine都共享同一块地址空间,Go语言也提供了锁原语来允许在goroutine之间安全地共享数据。然而,Go语言推荐的并发编程方式是在多个goroutine之间传递数据,而不是共享数据。

Go语言中的通道是一个双向或单向的通信管道,可以用于在多个goroutine之间交换(即发送和接收)数据。

goroutine和通道提供了一种轻量级的(即可扩展的)并发方式,其不会共享内存,所以不需要锁机制。虽然如此,但是与所有其它所有并发方式一样,创建并发程序时必须小心谨慎,并且对于并发程序来说,其维护工作通常也会比非并发程序更具挑战性。大多数操作系统都可以同时运行多个程序,所以我们可以利用这一点来减少维护工作,例如,可以通过运行多个程序(或同一个程序的多个副本)来处理不同的数据。一个优秀的 程序员 应当仅在其所具有的优势明显多于增加的维护成本时才会编写并发程序。

goroutine的创建语法如下:

    go function(arguments)
    go func(parameters) { block }(arguments)
 

我们必须要么调用一个现有的函数,要么调用一个当场创建的匿名函数。与其它函数一样,该函数可以有零个或多个参数,且在函数调用时必须传入对应的参数,如果有的话。被调用的函数会立即执行,当然是在另一个goroutine中执行,当前的goroutine(即,具有go语句的那一个)会从下一条语句处立即恢复。所以,在使用了go语句之后,程序中会至少有两个goroutine在运行,一个是原始的goroutine(main goroutine),另一个是新创建的goroutine。

应用程序启用大量的goroutine并等待其完成而相互之间不需要通信是不常见的。在大多数情况下,goroutine之间需要共同协作,因此我们可以给予它们相互通信的能力。下面的代码用于发送和接收数据:

    channel <- value   // Blocking send
    <-channel          // Receive and discard
    x := <-channel     // Receive and store
    x, ok := <-channel // As above & check for channel closed & empty
 

使用select语句和可缓冲的channel可以做到非阻塞的发送数据。

下面的代码使用内建的make()关键字创建channel。

    make(chan Type)
    make(chan Type, capacity)
 

如果未指定缓冲区容量, 则channel为同步channel,因此它将被阻塞直到发送方准备好发送数据且接收方准备好接收数据。如果指定了channel的容量,则该channel就是异步的,只要容量还未被发送者使用完或channel中还有接收方未接收的数据,则通信将在不阻塞的情况下进行。

默认情况下, channel是双向的,但是我们可以根据需要将channel定义为单向的。例如,我们可以以编译器强制的方式更好地表达我们的语义。在第7章中,我们将介绍如何创建并使用单向通道。

现在我们将在下面的小例子中演示一下刚刚上面提到的语法。我们会创建一个createCounter()函数,该函数将返回一个channel,每当我们要从中接收数据数据时,该channel都会发送一个int类型的数据。channel接收到的第一个值就是我们传入createCounter()函数的值,随后的每一个值都会比前一个大一。下面的代码演示了如何创建两个独立的counter channel(每个操作都在其独自的goroutine里运行),以及其产生的结果。

    counterA := createCounter(2)   // counterA is of type chan int
    counterB := createCounter(102) // counterB is of type chan int
    for i := 0; i < 5; i++ {
        a := <-counterA
        fmt.Printf("(A→%d, B→%d) ", a, <-counterB)
    }
    fmt.Println()
 
    (A→2, B→102) (A→3, B→103) (A→4, B→104) (A→5, B→105) (A→6, B→106)
 

我们用两种不同的方式展示了channel如何接收数据,第一种方式将接收到的数据赋值给了一个变量,第二种将接收到的数据作为一个参数传递给了一个函数。

对createCounter()函数的两次调用是在主goroutine中进行的,对于另外两个由createCounter()创建的goroutine,在初始时都处于被阻塞状态。在主goroutine中,只要我们尝试从其中一个goroutine中接收数据,发送方就会立即发送一个数据,接着发送goroutine再次处于阻塞状态,直到新的接收请求到来。这两个channel的容量是“无限大的”,因为它们可以一直发送数据。(当然,如果发送的值超出了int类型的大小,下一个值将从新开始。)一旦我们从每个channel中接收到了我们想要的五个数据,这两个channel就会再次处于阻塞状态并以备下次使用。

我们该如何处理不再使用的goroutine呢?这就需要我们先终止无限循环,这样可以不再发送数据,然后关闭正在使用的channel。我们将在下面的小节中演示这种方法,当然,这将会在第7章专门讨论。

func createCounter(start int) chan int {
    next := make(chan int)
    go func(i int) {
        for {
            next <- i
            i++
        }
    }(start)
    return next
}
 

上面的函数接收一个初始值并创建一个用于发送和接收int类型数据的channel。然后该函数在一个新建的goroutine中执行一个匿名函数,该匿名函数接收我们传入的初始值。同时该匿名函数有一个无限循环,并在每次迭代时将一个int类型数据发送给channel并自增加一。因为新建channel时没有指定容量,所以发送数据时会被阻塞,直到接收方从channel中接收数据。该阻塞状态仅仅影响该匿名函数所在的goroutine,所以程序其余部分会继续执行。一旦goroutine被设置为运行状态(当然,这里会立即被阻塞),函数的剩余部分会立即执行并将channel返回。

在某些情况下, 一个程序中可以有多个goroutine同时执行,每一个goroutine都有其channel,我们可以使用select语句来监视它们之间的通信。

目录


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

About Face 3

About Face 3

Alan Cooper、Robert Reimann、David Cronin / John Wiley & Sons / 2007-5-15 / GBP 28.99

* The return of the authoritative bestseller includes all new content relevant to the popularization of how About Face maintains its relevance to new Web technologies such as AJAX and mobile platforms......一起来看看 《About Face 3》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

MD5 加密
MD5 加密

MD5 加密工具