标签 Package 下的文章

Go 1.4中值得关注的几个变化

在Go 1.3发布半年过去后,Go核心项目组于本月初发布了Go 1.4 Beta1版本。这个版本的几个变化点虽然不是革命性的,但对后续Go语言的发展来说,打下了基础,定下了基调。这里就几个值得关注的变化点结合Go 1.4代码进行一些简单描述,希望大家能对Go 1.4有个感性的认知和了解。

Go 1.4依旧保持了Go 1兼容性的承诺,你的已有代码几乎无需任何改动就可以通过Go 1.4的编译并运行。(以下是我的测试环境:go version go1.3 darwin/amd64 vs. go version go1.4beta1 linux/amd64

一、语言变化

1、For-range循环

在Go 1.3及以前,for-range循环具有两种形式:

for k, v := range x {
    …
}

for k := range x {
    …
}

问题:如果我们不关心循环中的值,我们只关心循环本身,我们仍然要提供一个变量,或用_占位。

for _ = range x {
    …
}

下面这样的语法在Go 1.3及以前是无法编译通过的:

for range x {
    …
}

不过Go 1.4支持这种形式的语法,它使得代码更加clean,虽然它可能很少会被使用到。

例子:

//testforrange.go

package main

import "fmt"

func main() {
        var a [5]int = [5]int{2, 3, 4, 5, 6}
        for k, v := range a {
                fmt.Println(k, v)
        }

        for k := range a {
                fmt.Println(k)
        }

        for _ = range a {
                fmt.Println("print without care about the key and value")
        }

        for range a {
                fmt.Println("new syntax – print without care about the key and value")
        }
}

Go 1.3编译出错:

$go run testforrange.go
# command-line-arguments
./testforrange.go:19: syntax error: unexpected range, expecting {
./testforrange.go:22: syntax error: unexpected }

Go 1.4编译成功并输出正确结果:

0 2
1 3
2 4
3 5
4 6
0
1
2
3
4
print without care about the key and value
print without care about the key and value
print without care about the key and value
print without care about the key and value
print without care about the key and value
new syntax – print without care about the key and value
new syntax – print without care about the key and value
new syntax – print without care about the key and value
new syntax – print without care about the key and value
new syntax – print without care about the key and value

2、通过**T调用方法

下面这个例子:

package main

import "fmt"

type T int

func (T) M() {
        fmt.Println("Call M")
}

var x **T

func main() {
        x.M()
}

按照Go 1.4官方release note的说法,1.3版本及以前的gc和gccgo都会正常接受这种调用方式。但Go 1规范只允许自动在x前面加一个解引用,而不是两个,因此这个是有悖于定义的。Go 1.4强制禁止这种调用。

不过根据我实际的测试,Go 1.3和Go 1.4针对上面代码都会出现同样地编译错误。

$go run testdoubledeferpointer.go
# command-line-arguments
./testdoubledeferpointer.go:14: calling method M with receiver x (type **T) requires explicit dereference

二、支持的操作系统以及处理器体系架构的变化

这个无法演示。不过一个主要的变化就是Go 1.4可以构建出运行于ARM处理器Android操作系统上的二进制程序了。使用go.mobile库中的支持包,Go 1.4也可以构建出可以被Android应用加载的.so库。

三、兼容性变化

人们通过unsafe包并利用Go的内部实现细节和数据的机器表示形式来绕过Go语言类型系统的约束。Go的设计者们认为这是对Go兼容性规范的 不尊重,在Go 1.4中,Go核心组正式宣布unsafe code不再保证其兼容性。这次Go 1.4并没有针对此做任何代码变动,只是一个clarification而已。

四、实现和工具的变化

1、运行时(runtime)的变化

Go 1.3及以前版本,Go语言的runtime(垃圾收集、并发支持、interface管理、maps、slices、strings等)主要由C语言和 少量汇编语言实现的。在1.4版本中,很多代码被替换成了用Go自身实现,这样垃圾回收器可以扫描程序运行时栈,获取活跃变量的精确信息。这个变 化很大,但对程序应该没有语义上的影响。

这次重写使得垃圾回收器变得更加精确,这意味着它知道所有程序中活跃指针的位置。这些相关改变将减小heap的大小,总体上大约减少 10%~30%。

这样做的结果是栈也不再需要是分段的(segmented)了,消除了“hot split”的问题。如果一个stack到达了使用上限,Go将分配一个新的更大的stack,相应goroutine中的所有活跃的栈帧将被复制到新 stack上,所有指向栈的指针将被更新。在某些场景下,其性能将会变得显著提升,并且这样修改后,其性能更具可预测性。

连续栈(contiguous stacks)的使用使得栈的初始Size可以更小,在Go 1.4中goroutine的初始栈大小从8192字节缩小为2048字节。(正式发布时也许会改为4096)。

interface值类型的实现也做了调整。在之前的发布版中,interface值内部用一个字(word)来承载,要么是一个指针,要么是一 个单字(one-word)大小的纯量值,这取决于interface值变量中具体存储的是什么对象。这个实现会给垃圾收集器带来诸多困难,因此 在Go 1.4版本中interface值内部就用指针表示。在运行的程序中,绝大多数interface值都是指针,因此这个影响很小。不过那些在 interface值类型变量中存储整型值的程序将会有更多的内存分配。

2、gccgo的状态

Gcc和Go两个项目的发布计划不是同步的,GCC 4.9版本包含了实现了1.2规范的gccgo,下一个发布版gcc 5.0将可能包含实现了1.4规范的gccgo。

3、internal包(内部包)

Go以package为基本逻辑单元组织代码。Go 1.3及之前版本的Go语言实际上只支持两种形式Package内符号的可见性:本地的(unexported)和全局的(exported)。有些时候 我们希望一些包并非能被所有外部包所导入,但却能被其“临近”的包所导入和访问。但之前的Go语言不具备这种特性。Go 1.4引入了"internal"包的概念,导入这种internal包的规则约束如下:

如果导入代码本身不在以"internal"目录的父目录为root的目录树中,那么 不允许其导入路径(import path)中包含internal元素。

例如:
    – a/b/c/internal/d/e/f只可以被以a/b/c为根的目录树下的代码导入,不能被a/b/g下的代码导入。
    – $GOROOT/src/pkg/internal/xxx只能被标准库($GOROOT/src)中的代码所导入。(注:Go 1.4 取消了$GOROOT/src/pkg,标准库都移到$GOROOT/src下了)。
    – $GOROOT/src/pkg/net/http/internal只能被net/http和net/http/*的包所导入
    – $GOPATH/src/mypkg/internal/foo只能被$GOPATH/src/mypkg包的代码所导入

对于Go 1.4该规则首先强制应用于$GOROOT下。Go 1.5将扩展应用到$GOPATH下。

4、权威导入路径(import paths)

我们经常使用托管在公共代码托管服务中的代码,诸如github.com,这意味着包导入路径包含托管服务名,比如github.com/rsc /pdf。一些场景下为了不破坏用户代码,我们用rsc.io/pdf,屏蔽底层具体哪家托管服务,比如rso.io/pdf的背后可能是 github.com也可能是bitbucket。但这样会引入一个问题,那就是不经意间我们为一个包生成了两个合法的导入路径。如果一个程序中 使用了这两个合法路径,一旦某个路径没有被识别出有更新,或者将包迁移到另外一个不同的托管公共服务下去时,使用旧导入路径包的程序就会报错。

Go 1.4引入一个包字句的注释,用于标识这个包的权威导入路径。如果使用的导入的路径不是权威路径,go命令会拒绝编译。语法很简单:

package pdf // import "rsc.io/pdf"

如果pdf包使用了权威导入路径注释,那么那些尝试使用github.com/rsc/pdf导入路径的程序将会被go编译器拒绝编译。

这个权威导入路径检查是在编译期进行的,而不是下载阶段。

我们举个例子:

我们的包foo以前是放在github.com/bigwhite/foo下面的,后来主托管站换成了tonybai.com/foo,最新的 foo包的代码:

package foo // import "tonybai.com/foo"

import "fmt"

func Echo(a string) {
        fmt.Println("Foo:, a)
}

某个应用通过旧路径github.com/bigwhite/foo导入了该包:

//testcanonicalimportpath.go
package main

import "github.com/bigwhite/foo"

func main() {
        foo.Echo("Hello!")
}

我们编译该go文件,得到以下结果:

code in directory /home/tonybai/Test/Go/src/github.com/bigwhite/foo expects import "tonybai.com/foo"

5、go generate子命令

go 1.4中go工具集合新引入一个子命令:go generate,用于在编译前自动化生成某类代码。例如在.y上运行yacc编译器生成实现该语法的.go源文件。或是使用stringer工 具自动为常量生成String方法。这个命令并非由go tools(build, get等)自动执行,而必须显式执行。

不过我简单测试了一下,似乎这个命令设计文档中的:

// +build generate

并不好用啊。即便将其作为generate directive放入go源文件,该文件依旧会被go编译器当做正常go文件编译。Go 1.4标准库中使用go generate directive的有三个地方:

strconv/quote.go://go:generate go run makeisprint.go -output isprint.go
time/zoneinfo_windows.go://go:generate go run genzabbrs.go -output zoneinfo_abbrs_windows.go
unicode/letter.go://go:generate go run maketables.go -tables=all -output tables.go

通过go generate来实现泛型(generics)似乎不那么优雅啊。虽然设计者并非将其作为Go泛型的实现^_^。

6、源码布局变化

在Go自身源码库($GOROOT下)中,包的源码放在src/pkg中,这样做与其他库不同,包括Go自己的子库,比如go.tools。因此在Go 1.4中,pkg这一层目录树将被去除,比如fmt包的源码曾经放在src/pkg/fmt下,现在则放在src/fmt下。

五、性能

绝大多数程序使用1.4编译后的运行速度会与1.3的一致或略有提升,有些可能也会变得慢些。这次修改的较多,很难准确预测。

这次许多runtime的代码由C变为Go,这将导致一些heap大小有所缩减。另外这样做后有利于Go编译器的优化,诸如内联,会带来性能上的小幅提升。

垃圾回收器一方面得到了加速,使得重度依赖垃圾收集的程序得到可衡量的提升。但另外一方面,新的write barrier又引起了性能下降。提升和下降的量的多少取决于程序的行为。

Golang的演化历程

本文来自Google的Golang语言设计者之一Rob Pike大神在GopherCon2014大会上的开幕主题演讲资料“Hello, Gophers!”。Rob大神在这次分 享中用了两个生动的例子讲述了Golang的演化历程,总结了Golang到目前为止的成功因素,值得广大Golang Programmer & Beginner学习和了解。这里也用了"Golang的演化历程"作为标题。

1、Hello Gophers!

package main

import "fmt"

func main() {
    fmt.Printf("Hello, gophers!\n")
}

Rob大神的见面礼,后续会有针对这段的演化历史的陈述。

2、历史

这是一个历史性的时刻。

Golang已经获得了一定的成功,值得拥有属于自己的技术大会。

3、成功

促成这份成功的因素有许多:

    – 功能
    – 缺少的功能
    – 功能的组合
    – 设计   
    – 人
    – 时间

4、案例学习:两段程序

我们来近距离回顾两段程序。

第一个是你见过的第一个Go程序,是属于你的历史时刻。
第二个是我们见过的第一个Go程序,是属于全世界所有Gophers的历史时刻。

先看第一个“hello, world”

5、hello.b

main( ) {
    extrn a, b, c;
    putchar(a); putchar(b); putchar(c); putchar('!*n');
}
a 'hell';
b 'o, w';
c 'orld';

上面这段代码首先出现在1972年Brian W. Kernighan的B语言教程中(也有另外一说是出现在那之前的BCPL语言中)。

6、hello.c

main()
{
    printf("hello, world");
}

上面这段代码出现在1974年Brian W. Kernighan编写的《Programming in C: A Tutorial》中。这份教程当时是作为Unix v5文档的一部分。

7、hello.c

main()
{
    printf("hello, world\n"); //译注:与上面的hello.c相比,多了个换行符\n输出
}

这段代码首次出现在1978年Brian W. Kernighan和Dennis M. Ritchie合著的《The C Programming Language》一书中。

8、hello.c, 标准C草案

#include <stdio.h> //译注:与上面hello.c相比, 多了这个头文件包含

main()
{
    printf("hello, world\n");
}

这段代码出现在1988年Brian W. Kernighan和Dennis M. Ritchie合著的《The C Programming Language》第二版一书中,基于标准C草案。

9、hello.c,标准C89

#include <stdio.h>

main(void) //译注:与上面hello.c相比,多了个void
{
    printf("hello, world\n");
}

这段代码出现在1988年Brian W. Kernighan和Dennis M. Ritchie合著的《The C Programming Language》第二版第二次修订中。

10、一两代之后…

(省略所有中间语言)

关于Golang的讨论开始于2007年年末。

第一版语言规范起草于2008年3月份。

用于实验和原型目的的编译器开发工作已经展开。

最初的编译器输出的是C代码。

语言规范一形成,我们就重写了编译器,输出本地代码(机器码)。

11、hello.go, 2008年6月6日

package main

func main() int {
    print "hello, world\n";
    return 0;
}

针对首次提交代码的测试。

内置的print已经是当时的全部实现。main函数返回一个int类型值。
注意:print后面没有括号。

12、hello.go,2008年6月27日

package main

func main() {
    print "hello, world\n";
}

当main函数返回,程序调用exit(0)。

13、hello.go,2008年8月11日

package main

func main() {
    print("hello, world\n");
}

print调用加上了括号,这时print是一个函数,不再是一个原语。

14、hello.go,2008年10月24日

package main

import "fmt"

func main() {
    fmt.printf("hello, world\n");
}

我们熟知并喜欢的printf来了。

15、hello.go,2009年1月15日

package main

import "fmt"

func main() {
    fmt.Printf("hello, world\n");
}

头母大写的函数名用作才是导出的符号。

16、hello.go, 2009年12约11日

package main

import "fmt"

func main() {
    fmt.Printf("hello, world\n")
}

不再需要分号。

这是在2009年11月10日Golang开发发布后的一次重要改变。

这也是当前版本的hello, world

我们花了些时间到达这里(32年!)

都是历史了!

17、不仅仅有C

我们从"C"开始,但Go与C相比有着巨大的不同。

其他一些语言影响和贯穿于Go的设计当中。

    C: 语句和表达式语法
    Pascal: 声明语法
    Modula 2, Oberon 2:包
    CSP, Occam, Newsqueak, Limbo, Alef:  并发
    BCPL: 分号规则
    Smalltalk: 方法(method)
    Newsqueak: <-, :=
    APL: iota

   
等等。也有一些是全新发明的,例如defer、常量。

还有一些来自其他语言的优点和缺点:
    C++, C#, Java, JavaScript, LISP, Python, Scala, …

18、hello.go,Go 1版

将我们带到了今天。

package main

import "fmt"

func main() {
    fmt.Println("Hello, Gophers (some of whom know 中文)!")
}

我们来深入挖掘一下,把这段代码做一个拆解。

19、Hello, World的16个tokens

package
main
import
"fmt"
func
main
(
)
{
fmt
.
Println
(
"Hello, Gophers (some of whom know 中文)!"
)
}

20、package

早期设计讨论的主要话题:扩展性的关键

package是什么?来自Modula-2等语言的idea

为什么是package?

    – 拥有编译构建所需的全部信息
    – 没有循环依赖(import)
    – 没有子包
    – 包名与包路径分离
    – 包级别可见性,而不是类型级别
    – 在包内部,你拥有整个语言,在包外部,你只拥有包许可的东西。

21、main

一个C语言遗留风范尽显之处
最初是Main,原因记不得了。
主要的包,main函数
很特别,因为它是初始化树(initialization tree)的根(root)。

22、import

一种加载包的机制
通过编译器实现(有别于文本预处理器。译注:C语言的include是通过preprocessor实现的)
努力使其高效且线性
导入的一个包,而不是一个标识符(identifiers)集合(译注:C语言的include是将头文件里的标识符集合引入)
至于export,它曾经是一个关键字。

23、"fmt"

包路径(package path)只是一个字符串,并非标识符的列表。
让语言避免定义它的含义 – 适应性。(Allows the language to avoid defining what it means—adaptability)
从一开始就想要一个URL作为一个选项。(译注:类似import "github.com/go/tools/xxx)
可以应付将来的发展。

24、func

一个关键字,用于引入函数(类型、变量、常量),易于编译器解析。
对于函数字面量(闭包)而言,易于解析非常重要。
顺便说一下,最初这个关键字不是func,而是function。

小插曲:

Mail thread from February 6, 2008
From: Ken Thompson <ken@google.com>
To: gri, r
larry and sergey came by tonight. we 
talked about go for more than an hour. 
they both said they liked it very much.
p.s. one of larrys comments was "why isnt function spelled func?"

From: Rob Pike <r@google.com>

To: ken, gri
fine with me. seems compatible with 'var'.
anyway we can always say, "larry said to call it 'func'"

25、main

程序执行的起点。除非它不是。(译注:main不是起点,rob大神的意思是不是指下列情形,比如go test测试包,在google app engine上的go程序不需要main)
将初始化与正常执行分离,早期计划之中的。
初始化在哪里发生的?(译注:说的是package内的func init() {..}函数吧)
回到包设计。(译注:重温golang的package设计思想)

26、()

看看,没有void
main没有返回值,由运行时来处理main的返回后的事情。
没有函数参数(命令行选项通过os包获取)
没有返回值

返回值以及语法

27、{

用的是大括号,而不是空格(译注:估计是与python的空格缩进对比)
同样也不是方括号。
为什么在括号后放置换行符(newline)?

28、fmt

所有导入的标识符均限定于其导入的包。(All imported identifiers are qualified by their import.)
每个标识符要么是包或函数的本地变量,要么被类型或导入包限定。
对代码可读性的重大影响。

为什么是fmt,而不是format?

29、.

句号token在Go中有多少使用?(很多)
a.B的含义需要使用到类型系统
但这对于人类来说非常清晰,读起来也非常容易。
针对指针的自动转换(没有->)。

30、Println

Println,不是println,头母大写才是导出符号。
知道它是反射驱动的(reflection-driven)
可变参数函数
参数类型是(…); 2010年2月1日变成(…interface{})

31、(

传统函数语法

32、"Hello, Gophers (some of whom know 中文)!"

UTF-8编码的源码输入。字符串字面量也自动是utf8编码格式的。

但什么是字符串(string)呢?

首批写入规范的语法规则,今天很难改变了。(blog.golang.org/strings)

33、)

没有分号
在go发布后不久我们就去除了分号
早期曾胡闹地尝试将它们(译注:指得是括号)去掉
最终接受了BCPL的方案

34、}

第一轮结束。

旁白:还没有讨论到的

    – 类型
    – 常量
    – 方法
    – interface
    – 库
    – 内存管理
    – 并发(接下来将讨论)
   
外加工具,生态系统,社区等。
语言是核心,但也只是我们故事的一部分。

35、成功

要素:
    – 站在巨人的肩膀上(building on history)
    – 经验之作(building on experience) 译注:最初的三个神级语言设计者
    – 设计过程
    – 早期idea提炼到最终的方案中
    – 由一个小团队专门集中精力做
   
最终:承诺
    Go 1.0锁定了语言核心与标准库。

36、另一轮

让我们看第二个程序的类似演化过程。

37、问题:素数筛(Prime sieve)

问题来自于Communicating Sequential Processes, by C. A. R. Hoare, 1978。

“问题:以升序打印所有小于10000的素数。使用一个process数组:SIEVE,其中每个process从其前驱元素输入一个素数并打印它。接下 来这个process从其前驱元素接收到一个升序数字流并将它们传给其后继元素,这个过程会剔除掉所有是最初素数整数倍的数字。

38、解决方案

在1978年的CSP论文中。(注意不是Eratosthenes筛)

这个优美的方案是由David Gries贡献出来的。

39、CSP

在Hoare的CSP论文中:

[SIEVE(i:1..100)::
    p,mp:integer;
    SIEVE(i - 1)?p;
    print!p;
    mp := p; comment mp is a multiple of p;
*[m:integer; SIEVE(i - 1)?m →
    *[m > mp → mp := mp + p];
    [m = mp → skip
    ||m < mp → SIEVE(i + 1)!m
]   ]
||SIEVE(0)::print!2; n:integer; n := 3;
    *[n < 10000 → SIEVE(1)!n; n := n + 2]
||SIEVE(101)::*[n:integer;SIEVE(100)?n → print!n]
||print::*[(i:0..101) n:integer; SIEVE(i)?n → ...]
]

没有channel。能处理的素数的个数是在程序中指定的。

40、Newsqueak

circa 1988。

Rob Pike语言设计,Tom Cargill和Doug McIlroy实现。

使用了channels,这样个数是可编程的。(channel这个idea从何而来?)

counter:=prog(end: int, c: chan of int)
{
    i:int;
    for(i=2; i<end; i++)
        c<-=i;
};

filter:=prog(prime: int, listen: chan of int, send: chan of int)
{
    i:int;
    for(;;)
        if((i=<-listen)%prime)
            send<-=i;
};

sieve:=prog(c: chan of int)
{
    for(;;){
        prime:=<-c;
        print(prime, " ");
        newc:=mk(chan of int);
        begin filter(prime, c, newc);
        c=newc;
    }
};

count:=mk(chan of int);

begin counter(10000, count);
begin sieve(count);
"";

41、sieve.go,2008年3月5日

使用go规范编写的第一个版本,可能是第二个由go编写的重要程序。

>用于发送;<用于接收。Channel是指针。Main是头字母大写的。

package Main

// Send the sequence 2, 3, 4, … to channel 'ch'.
func Generate(ch *chan> int) {
    for i := 2; ; i++ {
        >ch = i;    // Send 'i' to channel 'ch'.
    }
}

// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
func Filter(in *chan< int, out *chan> int, prime int) {
    for ; ; {
        i := <in;    // Receive value of new variable 'i' from 'in'.
        if i % prime != 0 {
            >out = i;    // Send 'i' to channel 'out'.
        }
    }
}

// The prime sieve: Daisy-chain Filter processes together.
func Sieve() {
    ch := new(chan int);  // Create a new channel.
    go Generate(ch);      // Start Generate() as a subprocess.
    for ; ; {
        prime := <ch;
        printf("%d\n", prime);
        ch1 := new(chan int);
        go Filter(ch, ch1, prime);
        ch = ch1;
    }
}

func Main() {
    Sieve();
}

42. sieve.go,2008年7月22日

-<用于发送;-<用于接收。Channel仍然是指针。但现在main不是大写字母开头的了。

package main

// Send the sequence 2, 3, 4, … to channel 'ch'.
func Generate(ch *chan-< int) {
    for i := 2; ; i++ {
        ch -< i    // Send 'i' to channel 'ch'.
    }
}

// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
func Filter(in *chan<- int, out *chan-< int, prime int) {
    for {
        i := <-in;    // Receive value of new variable 'i' from 'in'.
        if i % prime != 0 {
            out -< i    // Send 'i' to channel 'out'.
        }
    }
}

// The prime sieve: Daisy-chain Filter processes together.
func Sieve() {
    ch := new(chan int);  // Create a new channel.
    go Generate(ch);      // Start Generate() as a subprocess.
    for {
        prime := <-ch;
        printf("%d\n",    prime);
        ch1 := new(chan int);
        go Filter(ch, ch1, prime);
        ch = ch1
    }
}

func main() {
    Sieve()
}

43、sieve.go,2008年9月17日

通信操作符现在是<-。channel仍然是指针。

package main

// Send the sequence 2, 3, 4, … to channel 'ch'.
func Generate(ch *chan <- int) {
    for i := 2; ; i++ {
        ch <- i  // Send 'i' to channel 'ch'.
    }
}

// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
func Filter(in *chan <- int, out *<-chan int, prime int) {
    for {
        i := <-in;  // Receive value of new variable 'i' from 'in'.
        if i % prime != 0 {
            out <- i  // Send 'i' to channel 'out'.
        }
    }
}

// The prime sieve: Daisy-chain Filter processes together.
func Sieve() {
    ch := new(chan int);  // Create a new channel.
    go Generate(ch);      // Start Generate() as a subprocess.
    for {
        prime := <-ch;
        print(prime, "\n");
        ch1 := new(chan int);
        go Filter(ch, ch1, prime);
        ch = ch1
    }
}

func main() {
    Sieve()
}

44、sieve.go,2009年1月6日

引入了make内置操作符。没有指针。编码错误!(有个*被留下了,错误的参数类型)

package main

// Send the sequence 2, 3, 4, … to channel 'ch'.
func Generate(ch chan <- int) {
    for i := 2; ; i++ {
        ch <- i  // Send 'i' to channel 'ch'.
    }
}

// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
func Filter(in chan <- int, out *<-chan int, prime int) {
    for {
        i := <-in;  // Receive value of new variable 'i' from 'in'.
        if i % prime != 0 {
            out <- i  // Send 'i' to channel 'out'.
        }
    }
}

// The prime sieve: Daisy-chain Filter processes together.
func Sieve() {
    ch := make(chan int);  // Create a new channel.
    go Generate(ch);       // Start Generate() as a subprocess.
    for {
        prime := <-ch;
        print(prime, "\n");
        ch1 := make(chan int);
        go Filter(ch, ch1, prime);
        ch = ch1
    }
}

func main() {
    Sieve()
}

45、sieve.go,2009年9月25日

第一个正确的现代版本。同样,大写头母不见了,使用了fmt。

package main

import "fmt"

// Send the sequence 2, 3, 4, … to channel 'ch'.
func generate(ch chan<- int) {
    for i := 2; ; i++ {
        ch <- i;    // Send 'i' to channel 'ch'.
    }
}

// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
func filter(src <-chan int, dst chan<- int, prime int) {
    for i := range src {    // Loop over values received from 'src'.
        if i%prime != 0 {
            dst <- i;    // Send 'i' to channel 'dst'.
        }
    }
}

// The prime sieve: Daisy-chain filter processes together.
func sieve() {
    ch := make(chan int);  // Create a new channel.
    go generate(ch);       // Start generate() as a subprocess.
    for {
        prime := <-ch;
        fmt.Print(prime, "\n");
        ch1 := make(chan int);
        go filter(ch, ch1, prime);
        ch = ch1;
    }
}

func main() {
    sieve();
}

46、sieve.go,2009年12月10日

分号不见了。程序已经与现在一致了。

package main

import "fmt"

// Send the sequence 2, 3, 4, … to channel 'ch'.
func generate(ch chan<- int) {
    for i := 2; ; i++ {
        ch <- i  // Send 'i' to channel 'ch'.
    }
}

// Copy the values from channel 'src' to channel 'dst',
// removing those divisible by 'prime'.
func filter(src <-chan int, dst chan<- int, prime int) {
    for i := range src {  // Loop over values received from 'src'.
        if i%prime != 0 {
            dst <- i  // Send 'i' to channel 'dst'.
        }
    }
}

// The prime sieve: Daisy-chain filter processes together.
func sieve() {
    ch := make(chan int)  // Create a new channel.
    go generate(ch)       // Start generate() as a subprocess.
    for {
        prime := <-ch
        fmt.Print(prime, "\n")
        ch1 := make(chan int)
        go filter(ch, ch1, prime)
        ch = ch1
    }
}

func main() {
    sieve()
}

这个优美的方案来自于几十年的设计过程。

47、旁边,没有讨论到的

select

真实并发程序的核心连接器(connector)
最初起源于Dijkstra的守卫命令(guarded command)
在Hoare的CSP理论实现真正并发。
经过Newsqueak、Alef、Limbo和其他语言改良后

2008年3月26日出现在Go版本中。

简单,澄清,语法方面的考虑。

48、稳定性

Sieve程序自从2009年末就再未改变过。– 稳定!

开源系统并不总是兼容和稳定的。

但,Go是。(兼容和稳定的)

这是Go成功的一个重要原因。

49、趋势

图数据展示了Go 1.0发布后Go语言的爆发。

50、成功

Go成功的元素:

    显然的:功能和工具。

    * 并发
    * 垃圾回收
    * 高效的实现
    * 给人以动态类型体验的静态类型系统
    * 丰富但规模有限的标准库
    * 工具化
    * gofmt
    * 在大规模系统中的应用

    不那么显然的:过程

    * 始终聚焦最初的目标
    * 在冻结后的集中开发
    * 小核心团队易于取得一致
    * 社区的重要贡献
    * 丰富的生态系统
   
总之,开源社区共享了我们的使命,聚焦于为当今的世界设计一门语言。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats