标签 标准库 下的文章

理解Golang包导入

Golang使用包(package)这种语法元素来组织源码,所有语法可见性均定义在package这个级别,与Java 、python等语言相比,这算不上什么创新,但与C传统的include相比,则是显得“先进”了许多。

Golang中包的定义和使用看起来十分简单:

通过package关键字定义包:
  
    package xxx

使用import关键字,导入要使用的标准库包或第三方依赖包。

   import "a/b/c"
   import "fmt"

   c.Func1()
   fmt.Println("Hello, World")

很多Golang初学者看到上面代码,都会想当然的将import后面的"c"、"fmt"当成包名,将其与c.Func1()和 fmt.Println()中的c和fmt认作为同一个语法元素:包名。但在深入Golang后,很多人便会发现事实上并非如此。比如在使用实时分布式消 息平台nsq提供的go client api时:

我们导入的路径如下:

   import “github.com/bitly/go-nsq”

但在使用其提供的export functions时,却用nsq做前缀包名:

   q, _ := nsq.NewConsumer("write_test", "ch", config)

人们不禁要问:import后面路径中的最后一个元素到底代表的是啥? 是包名还是仅仅是一个路径?我们一起通过试验来理解一下。  实验环境:darwin_amd64 , go 1.4

初始试验环境目录结果如下:

GOPATH = /Users/tony/Test/Go/pkgtest/
pkgtest/
    pkg/
    src/
       libproj1/
           foo/
              foo1.go
       app1/
           main.go

   
一、编译时使用的是包源码还是.a

我们知道一个非main包在编译后会生成一个.a文件(在临时目录下生成,除非使用go install安装到$GOROOT或$GOPATH下,否则你看不到.a),用于后续可执行程序链接使用。

比如Go标准库中的包对应的源码部分路径在:$GOROOT/src,而标准库中包编译后的.a文件路径在$GOROOT/pkg/darwin_amd64下。一个奇怪的问题在我脑袋中升腾起来,编译时,编译器到底用的是.a还是源码?

我们先以用户自定义的package为例做个小实验。

$GOPATH/src/
    libproj1/foo/
            – foo1.go
    app1
            – main.go

//foo1.go
package foo

import "fmt"

func Foo1() {
    fmt.Println("Foo1")
}

// main.go
package main

import (
    "libproj1/foo"
)

func main() {
    foo.Foo1()
}

执行go install libproj1/foo,Go编译器编译foo包,并将foo.a安装到$GOPATH/pkg/darwin_amd64/libproj1下。
编译app1:go build app1,在app1目录下生成app1*可执行文件,执行app1,我们得到一个初始预期结果:

$./app1
Foo1

现在我们无法看出使用的到底是foo的源码还是foo.a,因为目前它们的输出都是一致的。我们修改一下foo1.go的代码:

//foo1.go
package foo

import "fmt"

func Foo1() {
    fmt.Println("Foo1 – modified")
}

重新编译执行app1,我们得到结果如下:

$./app1
Foo1 – modified

实际测试结果告诉我们:(1)在使用第三方包的时候,当源码和.a均已安装的情况下,编译器链接的是源码。

那么是否可以只链接.a,不用第三方包源码呢?我们临时删除掉libproj1目录,但保留之前install的libproj1/foo.a文件。

我们再次尝试编译app1,得到如下错误:

$go build app1
main.go:5:2: cannot find package "libproj1/foo" in any of:
    /Users/tony/.Bin/go14/src/libproj1/foo (from $GOROOT)
    /Users/tony/Test/Go/pkgtest/src/libproj1/foo (from $GOPATH)

编译器还是去找源码,而不是.a,因此我们要依赖第三方包,就必须搞到第三方包的源码,这也是Golang包管理的一个特点。

其实通过编译器的详细输出我们也可得出上面结论。我们在编译app1时给编译器传入-x -v选项:

$go build -x -v app1
WORK=/var/folders/2h/xr2tmnxx6qxc4w4w13m01fsh0000gn/T/go-build797811168
libproj1/foo
mkdir -p $WORK/libproj1/foo/_obj/
mkdir -p $WORK/libproj1/
cd /Users/tony/Test/Go/pkgtest/src/libproj1/foo
/Users/tony/.Bin/go14/pkg/tool/darwin_amd64/6g -o $WORK/libproj1/foo.a -trimpath $WORK -p libproj1/foo -complete -D _/Users/tony/Test/Go/pkgtest/src/libproj1/foo -I $WORK -pack ./foo1.go ./foo2.go
app1
mkdir -p $WORK/app1/_obj/
mkdir -p $WORK/app1/_obj/exe/
cd /Users/tony/Test/Go/pkgtest/src/app1
/Users/tony/.Bin/go14/pkg/tool/darwin_amd64/6g -o $WORK/app1.a -trimpath $WORK -p app1 -complete -D _/Users/tony/Test/Go/pkgtest/src/app1 -I $WORK -I /Users/tony/Test/Go/pkgtest/pkg/darwin_amd64 -pack ./main.go
cd .
/Users/tony/.Bin/go14/pkg/tool/darwin_amd64/6l -o $WORK/app1/_obj/exe/a.out -L $WORK -L /Users/tony/Test/Go/pkgtest/pkg/darwin_amd64 -extld=clang $WORK/app1.a
mv $WORK/app1/_obj/exe/a.out app1

可以看到编译器6g首先在临时路径下编译出依赖包foo.a,放在$WORK/libproj1下。但我们在最后6l链接器的执行语句中并未显式看到app1链接的是$WORK/libproj1下的foo.a。但是从6l链接器的-L参数来看:-L $WORK -L /Users/tony/Test/Go/pkgtest/pkg/darwin_amd64,我们发现$WORK目录放在了前面,我们猜测6l首先搜索到的时$WORK下面的libproj1/foo.a。

为了验证我们的推论,我们按照编译器输出,按顺序手动执行了一遍如上命令,但在最后执行6l命令时,去掉了-L $WORK:

/Users/tony/.Bin/go14/pkg/tool/darwin_amd64/6l -o $WORK/app1/_obj/exe/a.out -L /Users/tony/Test/Go/pkgtest/pkg/darwin_amd64 -extld=clang $WORK/app1.a

这样做的结果是:

$./app1
Foo1

编译器链接了$GOPATH/pkg下的foo.a。(2)到这里我们明白了所谓的使用第三方包源码,实际上是链接了以该最新源码编译的临时目录下的.a文件而已。

Go标准库中的包也是这样么?对于标准库,比如fmt而言,编译时,到底使用的时$GOROOT/src下源码还是$GOROOT/pkg下已经编译好的.a呢?

我们不妨也来试试,一个最简单的hello world例子:
//main.go
import "fmt"

func main() {
    fmt.Println("Hello, World")
}

我们先将$GOROOT/src/fmt目录rename 为fmtbak,看看go compiler有何反应?
go build -x -v ./

$go build -x -v ./
WORK=/var/folders/2h/xr2tmnxx6qxc4w4w13m01fsh0000gn/T/go-build957202426
main.go:4:8: cannot find package "fmt" in any of:
    /Users/tony/.Bin/go14/src/fmt (from $GOROOT)
    /Users/tony/Test/Go/pkgtest/src/fmt (from $GOPATH)
 
找不到fmt包了。显然标准库在编译时也是必须要源码的。不过与自定义包不同的是,即便你修改了fmt包的源码(未重新编译GO安装包),用户源码编译时,也不会尝试重新编译fmt包的,依旧只是在链接时链接已经编译好的fmt.a。通过下面的gc输出可以验证这点:

$go build -x -v ./
WORK=/var/folders/2h/xr2tmnxx6qxc4w4w13m01fsh0000gn/T/go-build773440756
app1
mkdir -p $WORK/app1/_obj/
mkdir -p $WORK/app1/_obj/exe/
cd /Users/tony/Test/Go/pkgtest/src/app1
/Users/tony/.Bin/go14/pkg/tool/darwin_amd64/6g -o $WORK/app1.a -trimpath $WORK -p app1 -complete -D _/Users/tony/Test/Go/pkgtest/src/app1 -I $WORK -pack ./main.go
cd .
/Users/tony/.Bin/go14/pkg/tool/darwin_amd64/6l -o $WORK/app1/_obj/exe/a.out -L $WORK -extld=clang $WORK/app1.a
mv $WORK/app1/_obj/exe/a.out app1

可以看出,编译器的确并未尝试编译标准库中的fmt源码。

二、目录名还是包名?

从第一节的实验中,我们得知了编译器在编译过程中依赖的是包源码的路径,这为后续的实验打下了基础。下面我们再来看看,Go语言中import后面路径中最后的一个元素到底是包名还是路径名?

本次实验目录结构:
$GOPATH
    src/
       libproj2/
             foo/
               foo1.go
       app2/
             main.go

按照Golang语言习惯,一个go package的所有源文件放在同一个目录下,且该目录名与该包名相同,比如libproj1/foo目录下的package为foo,foo1.go、 foo2.go…共同组成foo package的源文件。但目录名与包名也可以不同,我们就来试试不同的。

我们建立libproj2/foo目录,其中的foo1.go代码如下:

//foo1.go
package bar

import "fmt"

func Bar1() {
    fmt.Println("Bar1")
}

注意:这里package名为bar,与目录名foo完全不同。

接下来就给app2带来了难题:该如何import bar包呢?

我们假设import路径中的最后一个元素是包名,而非路径名。

//app2/main.go

package main

import (
    "libproj2/bar"
)

func main() {
    bar.Bar1()
}

编译app2:

$go build -x -v app2
WORK=/var/folders/2h/xr2tmnxx6qxc4w4w13m01fsh0000gn/T/go-build736904327
main.go:5:2: cannot find package "libproj2/bar" in any of:
    /Users/tony/.Bin/go14/src/libproj2/bar (from $GOROOT)
    /Users/tony/Test/Go/pkgtest/src/libproj2/bar (from $GOPATH)

编译失败,在两个路径下无法找到对应libproj2/bar包。

我们的假设错了,我们把它改为路径:

//app2/main.go

package main

import (
    "libproj2/foo"
)

func main() {
    bar.Bar1()
}

再编译执行:

$go build app2
$app2
Bar1

这回编译顺利通过,执行结果也是OK的。这样我们得到了结论:(3)import后面的最后一个元素应该是路径,就是目录,并非包名

go编译器在这些路径(libproj2/foo)下找bar包。这样看来,go语言的惯例只是一个特例,即恰好目录名与包名一致罢了。也就是说下面例子中的两个foo含义不同:

import "libproj1/foo"

func main() {
    foo.Foo()
}

import中的foo只是一个文件系统的路径罢了。而下面foo.Foo()中的foo则是包名。而这个包是在libproj1/foo目录下的源码中找到的。

再类比一下标准库包fmt。

import "fmt"
fmt.Println("xxx")

这里上下两行中虽然都是“fmt",但同样含义不同,一个是路径 ,对于标准库来说,是$GOROOT/src/fmt这个路径。而第二行中的fmt则是包名。gc会在$GOROOT/src/fmt路径下找到fmt包的源文件。

三、import m "lib/math"

Go language specification中关于import package时列举的一个例子如下:

Import declaration          Local name of Sin

import   "lib/math"         math.Sin
import m "lib/math"         m.Sin
import . "lib/math"         Sin

我们看到import m "lib/math"  m.Sin一行。我们说过lib/math是路径,import语句用m替代lib/math,并在代码中通过m访问math包中的导出函数Sin。

那m到底是包名还是路径呢?既然能通过m访问Sin,那m肯定是包名了,Right!那import m "lib/math"该如何理解呢? 

根据上面一、二两节中得出的结论,我们尝试理解一下m:(4)m指代的是lib/math路径下唯一的那个包

一个目录下是否可以存在两个包呢?我们来试试。

我们在libproj1/foo下新增一个go源文件,bar1.go:

package bar

import "fmt"

func Bar1() {
    fmt.Println("Bar1")
}

我们重新构建一下这个目录下的包:

$go build libproj1/foo
can't load package: package libproj1/foo: found packages bar1.go (bar) and foo1.go (foo) in /Users/tony/Test/Go/pkgtest/src/libproj1/foo

我们收到了错误提示,编译器在这个路径下发现了两个包,这是不允许的。

我们再作个实验,来验证我们对m含义的解释。

我们建立app3目录,其main.go的源码如下:

//main.go
package main

import m "libproj2/foo"

func main() {
    m.Bar1()
}

libproj2/foo路径下的包的包名为bar,按照我们的推论,m指代的就是bar这个包,通过m我们可以访问bar的Bar1导出函数。

编译并执行上面main.go:

$go build app3
$app3
Bar1

执行结果与我们推论完全一致。

附录:6g, 6l文档位置:

6g – $GOROOT/src/cmd/gc/doc.go
6l – $GOROOT/src/cmd/ld/doc.go

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