goroutine的用法非常简单:只要把go关键字添加到任意一个具名函数或者匿名函数的前面,该函数就会成为一个goroutine。作为例子,代码清单9-1展示了如何在名为goroutine.go的文件中创建goroutine

代码清单9-1 goroutine使用示例

package main

func printNumbers1() {  
    for i := 0; i < 10; i++ {
        fmt.Printf("%d ", i)  
    }
}

func printLetters1() {  
    for i := 'A'; i < 'A'+10; i++ {    
        fmt.Printf("%c ", i)  
    }
}

func print1() {  
    printNumbers1()  
    printLetters1()
}

func goPrint1() {  
    go printNumbers1()  
    go printLetters1()
}

func main() {
}

goroutine.go文件中定义了printNumbers1printLetters1两个函数,分别用于循环并打印数字和英文字母,其中printNumbers1会打印从0到9的所有数字,而printLetters1则会打印从A到J的所有英文字母。除此之外,goroutine.go文件中还定义了print1goPrint1两个函数,前者会依次调用printNumbers1printLetters1,而后者则会以goroutine的形式调用printNumbers1printLetters1

为了检测这个程序的运行时间,我们将通过测试而不是main函数来运行程序中的print1函数和goPrint1函数。这样一来,我们就不必为了测量这两个函数的运行时间而编写测量代码,这也避免了因为编写计时代码而导致测量不准确的问题。

代码清单9-2展示了测试用例的具体代码,这些代码单独记录在了goroutine_test.go文件当中。

代码清单9-2 运行goroutine示例的测试文件

package main

import "testing" 

func TestPrint1(t *testing.T) {// 测试顺序执行的函数  
    print1()
}

func TestGoPrint1(t *testing.T) {// 测试对以goroutine形式执行的函数
    goPrint1()
}

通过使用以下命令执行这一测试:

go test –v

我们将得到以下结果:

=== RUN TestPrint1
0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J --- PASS: 
TestPrint1 (0.00s)
=== RUN TestGoPrint1
--- PASS: TestGoPrint1 (0.00s)
PASS

注意,第二个测试用例并没有产生任何输出,这是因为该用例在它的两个goroutine能够产生输出之前就已经结束了。为了让第二个测试用例能够正常地产生输出,我们需要使用time包中的Sleep函数,在第二个测试用例的末尾加上一些延迟:

func TestGoPrint1(t *testing.T) {    
    goPrint1()    
    time.Sleep(1 * time.Millisecond)
}

这样一来,第二个测试用例就会在该测试用例结束之前正常地产生输出了:

=== RUN TestPrint1
0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J --- PASS: 
TestPrint1 (0.00s)
=== RUN TestGoPrint1
0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J --- PASS: 
TestGoPrint1 (0.00s)
PASS

这两个测试用例都产生了相同的结果。初看上去,是否使用goroutine似乎并没有什么不同,但事实上,这两个测试用例之所以会产生相同的结果,是因为printNumbers1函数和printLetters1函数都运行得如此之快,所以是否以goroutine形式运行它们并不会产生任何区别。为了更准确地模拟正常的计算任务,我们将通过time包中的Sleep函数人为地给这两个函数加上一点延迟,并把带有延迟的函数重新命名为printNumbers2printLetters2。代码清单9-3展示了这两个新函数,跟原来的函数一样,它们也会被放在goroutine.go文件中。

代码清单9-3 模拟执行计算任务的goroutine

func printNumbers2() {  
    for i := 0; i < 10; i++ {    
        time.Sleep(1 * time.Microsecond)    
        fmt.Printf("%d ", i)  
    }
}//添加1 μs的延迟,用于模拟计算任务

func printLetters2() {
    for i := 'A'; i < 'A'+10; i++ {
        time.Sleep(1 * time.Microsecond)
        fmt.Printf("%c ", i)
    }
}

func goPrint2() {  
    go printNumbers2()  
    go printLetters2()
}

新定义的两个函数通过在每次迭代中添加1s的延迟来模拟计算任务。为了测试新添加的goPrint2函数,我们将在goroutine_test.go文件中添加相应的测试用例,并且和之前一样,为了让被测试的函数能够正常地产生输出,测试用例将在调用goPrint2函数之后等待1μs:

func TestGoPrint2(t *testing.T) {    
    goPrint2()    
    time.Sleep(1 * time.Millisecond)
}

现在,运行测试用例将得到以下输出:

=== RUN TestPrint1
0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J --- PASS: 
TestPrint1 (0.00s)
=== RUN TestGoPrint1
0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J --- PASS: 
TestGoPrint1 (0.00s)
=== RUN TestGoPrint2
A 0 B 1 C D 2 E 3 F 4 G H 5 I 6 J 7 8 9 --- PASS: 
TestGoPrint2 (0.00s)
PASS

注意看TestGoPrint2函数的输出结果,从结果可以看出,程序这次并不是先执行printNumbers2函数,然后再执行printLetters2函数,而是交替地执行它们!

如果我们再执行一次这个测试,那么TestGoPrint2函数的输出结果的最后一行可能会有所不同:这是因为printNumbers2printLetters2都是独立运行的,并且它们都在争先恐后地想要将自己的结果输出到屏幕上,所以随着这两个函数的执行顺序不同,测试产生的结果也会有所不同。唯一的例外是,如果你使用的是Go 1.5之前的版本,那么你每次执行这个测试都会得到相同的结果。

之所以会出现这种情况,是因为Go 1.5之前的版本在用户没有另行设置的情况下,即使计算机拥有多于一个CPU,它默认也只会使用一个CPU。但是从Go 1.5开始,这一情况发生了改变——Go运行时环境会使用计算机拥有的全部CPU。在Go 1.5或以后的版本中,用户如果想要让Go运行时环境只使用一个CPU,就需要执行以下命令:

go test -run x -bench . –cpu 1

在执行了这个命令之后,每次执行TestGoPrint2都将得到完全相同的结果。

results matching ""

    No results matching ""