下载网站源码分享原包?app下载网站源码

大家好,如果您还对下载网站源码分享原包不太了解,没有关系,今天就由本站为大家分享下载网站源码分享原包的知识,包括app下载网站源码的问题都会给大家分析到,还望可以解决大家的问题,下面我们就开始吧!

作者|郝林

编辑|小智

2007年9月20日,关于设计一门全新语言的讨论正式开始,这门全新的语言,就是后来的Go。时至今日,Go语言已经发布到1.9版本,走过了整整十年的历程。在这十年间,Go语言两夺TIOBE年度语言大奖(2009/2016),许多初创公司在早期使用Go进行开发,包括现在的云计算巨头Docker,也由此催生出了Kubernetes这样的项目。本文就将带你回顾Go语言的2017发展及其未来展望。

写在前面

Google的Go语言团队会在2018年初宣布Go1.10正式版的发布。10这个二位数也预示着Go语言的进一步成熟。据说,Go2也被提上了日程。Go2将会是大神们对Go语言的一次彻底反思和改进。虽然现在细节还没有被暴露出来,但是这已经足以让Gopher(Go语言爱好者)们激动不已了。会有泛型支持吗?GC会变革吗?详细调参会可行吗?各种猜测已经在各个论坛和群组里层出不穷了。

不过,饭要一口一口吃,肌肉要一点一点练。在憧憬未来之前,先让我们看看Go语言在2017年的表现。

首先,根据GoogleTrends的统计结果(https://trends.google.com/trends/explore?q=golang&hl=en-US),我们可以看到Go语言在过去一年中的流行程度是稳中有升。

图1Go语言在2017年的流行趋势

初看起来,Go语言在2017年表现得比较平淡。但是,让我们把时间线拉长。

图2Go语言在过去5年间的流行趋势

你一定看到了,Go语言在2017年的“上升”是对近年来的一种延续。有些编程语言会随着其擅长领域的爆红或遭冷而大起大落。但是Go语言不会。这就像它朴素的编程风格一样。它的使用范围很广,我几乎可以在任何场景下使用它。

Go语言的适用范围一直在不断地扩大。经过广大开发者的共同努力,它已开始涉足在当前大热的数据科学和机器学习领域。虽然还只是开始,但是就像我在《Go并发编程实战》第2版的扉页中说的那样,我“深信Go语言在人工智能时代和机器人时代也能大放异彩”。

更令人欣慰的是,中国的开发者对于Go语言的流行起着至关重要的作用。在过去的一年里,我们的热衷和贡献依然领跑全世界!

图3Go语言在2017年的流行区域热图

回顾

我在前年和去年分别写过两篇相关的文章——《解读2015之Golang篇:Golang的全迸发时代》和《解读2016之Golang篇:极速提升,逐步超越》。大家可以把它们当做一个系列的记录参看。

类型别名

类型别名(typealiases)原本是要在Go1.8发布时推出的。但是由于一些争议和实现上的问题,Go团队把它推迟到了Go1.9。

这一特性其实是为开发者们的代码库重构准备的。代码重构是对代码的重新组织,以及这种重组与代码包之间的关系的重新思考和修改过程。代码重构的原因可能是代码的拆分、命名优化或者依赖关系清理,等等。但是不论原因是什么,我们的目的都是让代码变得更清楚、更易于使用,以及更容易扩展。

也许你有过这样的经历:在进行(范围比较广的)代码重构的过程中,原有代码已经改变。甚至,当你完成重构并想把代码合并回去的时候,原有代码已经面目全非。合并代码有时候比重构代码更加困难。

这就引出了一个比较先进的重构方法——渐进式代码重构。也就是说,同时计划重构的终极目标和阶段性目标。在达到完备状态之前,设置几个易实施并且可用的中间状态。纵观Go语言的代码更新和版本升级过程,我们不难发现Go团队对这种方法的合理运用。

然而,在渐进式代码重构的过程中我们也可能会遇到问题。例如,当你想把一个允许包外代码访问的类型迁移到另外一个包中时,该怎么做?简单来说,应该分为三步:1)在目的包中声明一个名称和功能都相同的新类型。2)检查所有可能引用原类型的地方,并把那些引用都指向新类型。3)删除原包中的那个类型。如果这个类型只被由你掌控的程序中引用,那么这种方式当然没问题。但是,如果该类型所在的是一个已被广泛依赖的底层代码包,那我劝你还是不要执行第3步了。除非你冒着被口水淹死的风险发布程序不兼容声明。可是不执行第3步就等于任由废弃代码在程序中蔓延。这也是很恶心的一件事。

讲了这么多,我要说的重点是:Go的类型别名就是为我们解决这类两难的问题的。在类型别名真正问世之前,Go团队自己在做上述重构时都不得不用上一些特殊的、脆弱的手段。

若使用类型别名的话,应该怎么做?如果我们要把oldpkg.OldType类型迁移到newpkg代码包中并将其改名为NewType,那么最少用2步就可以完成:1)声明newpkg.NewType类型。2)把oldpkg.OldType类型的声明改为:

packageoldpkgtypeOldType=newpkg.NewType

新的oldpkg.OldType类型声明可以使它与newpkg.NewType完全等价,并可以实现互换。也就是说,如果一个函数有一个oldpkg.OldType类型的参数声明,那么该函数就可以接受一个newpkg.NewType类型的参数值。

当然,为了不留废弃代码你仍然需要在某个时间删除掉oldpkg.OldType。但是类型别名给了你和其他使用oldpkg.OldType的开发者一个可以游刃有余的必要条件。你们为重构代码而设置的中间状态都可以轻松达成。

举个例子,你在按照上述2步迁移完类型之后,可以马上把自己写的某个函数的声明由

packagehandlerimportoldpkgfuncHandleXXX(objoldpkg.OldType){}

改为

packagehandlerimportnewpkgfuncHandleXXX(objnewpkg.NewType){}

然而,在调用该函数的(其他人写的)程序那里,却可以不用做任何修改(虽然最后可能要修改)。代码handler.HandleXXX(oldTypeVar)仍然有效。其中的oldTypeVar是oldpkg.OldType类型的值。这就相当于可以让各方按照自己的节奏重构代码了。

再后面的情景可能是:你在你的代码包还是1.0版本的时候就发布声明说oldpkg.OldType类型即将在2.0版本中删除。而当你在开发2.0版本时,心安理得地删掉了oldpkg.OldType。

实际上,类型别名只是Go团队为了让我们顺利实施大规模Go软件工程的举措之一。他们一直在致力于帮助开发者们高效地编写Go代码和利用Go代码包,并避免因不经意的重复造轮子而导致的代码膨胀。如果你真正用过Go语言,那么也一定能体会到它在代码依赖方面展现出的规范性和严谨性。

sync.Map

广大gopher们又迎来了一个提供并发安全性的高级数据结构——sync.Map。这个数据结构提供了一些常用的键、值存取操作方法,并保证了这些操作的原子性。同时,它也保证了存取的性能——算法复杂度依旧是O(1)的。相信很多人已经期盼了很久。不过请注意,它是Go语言标准库中的一员,而不是语言层面的东西。也正因为这一点,Go对它的键类型和值类型并无程序编译期的类型检查。我们只能在程序运行期自行保证键、值类型的正确。

如果你一直关注Go语言,可能已经猜到它就是之前一直蛰伏在golang.org/x/sync/syncmap包中的那个struct。现在它被纳入了标准库,并将会获得更多的底层优化的可能。

在sync.Map问世之前,我们如果需要并发安全的字典结构,那么就需要自行搭建。这其实也不是麻烦事,使用sync.Mutex(互斥锁)或sync.RWMutex(读写锁)在再加上原生的数据类型map就可以轻松办到。Github网站上就有很多库提供了类似的数据结构。我在《Go并发编程实战》第2版中也提供了一个实现较完整的并发安全字典。它的性能比同类的第三方数据结构还要好一些。因为它在很大程度上有效地避免了对锁的依赖。

大家应该都知道,使用锁就意味着要把一些并发的操作强制串行化。这对程序的性能是有很大的负面影响的,尤其是在有多个CPU核心的情况下。因此我们常说,能用原子操作就不要用锁。可惜前者只对一些基本数据结构提供支持。

不管是哪一种操作,它们在多个CPU核心面前都是相对低效的。因为只要是对共享状态(比如多个线程都可见的同一个变量)的存取就会涉及到状态的同步。在CPU层面,这种同步就是cache级别的,也可称之为cachecontention。你一定听说过CPU的L1cache和L2cache。举个例子,如果在不同CPU中运行的线程同时在操作同一个锁(更确切地说是锁中的计数变量,具体可参看sync.Mutex的源码),那么它们会争相声明存在于自己的cache中的那个变量的值是唯一有效的(或者说是最新的)。一旦有一个线程声明成功,那么运行于其他CPU核心中的线程再想存取相同的变量就必须先从前者那里做同步。这个同步的耗时在CPU层面是很可观的。

那么,sync.Map帮我们解决上述问题了吗?很遗憾,答案是没有完全解决。实际上,这根本就不是在应用层面(甚至操作系统层面)可以完全解决的问题。我们只能经过一轮又一轮的优化获得更高的性能,或者说逐渐逼近最高性能。

sync.Map在内部使用了大量的原子操作存取键和值,并利用两个map作为存储介质。

其中一个map(字段read)可被视作一个快照。这个快照也可被称为只读字典。它保存了在前一个操作周期结束时sync.Map值中包含的所有键值对。虽然其中的键所对应的值都可以被更改,但是键绝不会有增减。因此,这里的“只读”是对其中的键的集合而言的。如此一来,对只读字典的操作无需使用锁。实际上,在sync.Map的增、删、改、查方法中会首先尝试操作只读字典。

另一个map(字段dirty)中存有最新的键值对的集合。它也可被称为脏字典。新的键值对会首先被存储到该字典中。sync.Map中所有的增、删、改、查方法在操作脏字典时都需要在锁(字段mu)的保护下进行。

在达到当前操作周期的边界的时候,sync.Map会把脏字典提升为只读字典,并把存储脏字典的字段设置为nil。当再有新的键值对存入时,sync.Map会对脏字典进行重新初始化,并把只读字典(前一个操作周期中的脏字典)中的所有键值对反灌入脏字典,最后把新的键值对也加入其中。在新的操作周期中,(新的)只读字典和(新的)脏字典依然分别代表着键值对集合的快照和最新版本。如此一来,通过两个字典之间的周期性同步,sync.Map就实现了键值对操作的“快路径”和“慢路径”。“快路径”就意味着无锁化操作,而“慢路径”仅在“快路径”不通时才会被考虑。

顺便说一句,sync.Map在判断当前操作周期的边界达到与否时依据的是一个记录着“在只读字典中未找到被查询键”这种情况的发生次数的计数值(字段misses)。一旦这个计数值等于脏字典的长度,就意味着一个操作周期的结束。显然,操作周期会随着脏字典的增大而变长。

图4从键值对的流转方式看sync.Map

上图从另外一个角度展现了sync.Map存取键值对的方式。

总体上讲,sync.Map的内部实现就是如此。如果你想探究细节可以查看Go语言标准库代码包sync中的map.go文件。

通过对sync.Map的实现的理解,我们就可以分析出它的优势和劣势。显然,sync.Map更适合于键的集合相对固定的场景。这时只有一些必要的原子操作会对性能有轻微的影响。更具体地讲,如果所有的键值对都在初始化sync.Map值时加入,之后仅有键值对的读取和更新而没有添加,那么sync.Map就会发挥出很高的性能。当然,如果在多个线程中同时对同一个键的值进行更新,那么还会存在由原子操作引发的竞争。这也会涉及到cachecontention。

另一方面,如果在使用过程中有非常多的新键存入的话,那么在极端情况下它的性能很可能会回退至map+sync.Mutex/sync.RWMutex的水平,甚至还会不如后者,别忘了其中还有很多原子操作。另外,请注意,sync.Map所占用的空间比map要多。至于多多少,还要看实际的键值对获取情况。

以上就是我对sync.Map的简单剖析。希望可供大家在选择字典的并发安全策略时参考。

其他值得注意的改进

并行编译

Go1.9默认会并行地编译你的代码。不过前提是你的机器上多个CPU核心。其实在这之前并行编译也是存在的,只不过那时的粒度是代码包。也就是说,不同的代码包可被并行地编译。但是Go1.9的并行编译粒度是函数级别的。这显然可以使编译更加迅捷。

关于vendor目录的处理

之前我们执行gobuild./…命令的时候,当前目录内的vendor目录也会被编译。但是现在必须输入gobuild./vendor/…才可以。对于其他的Go标准命令也是如此。我就为此烦恼过,所以很高兴看到这样的变化。请想象一下,当你在执行gotest./…的时候,Go也会测试你依赖的那一坨代码包。在很多时候这根本就没有必要,白白浪费时间。

关于GC

我们都知道,Go的GC是并发的。但这只限于自动GC。当我们手动触发GC时,Go运行时系统依然会“Stoptheworld”(或者说停止内部调度)。值得庆幸的是,Go1.9为我们带来了对手动GC的并发支持!这涉及到了runtime.GC、debug.SetGCPercent和debug.FreeOSMemory函数。不过要注意,调用这些函数的goroutine是会被阻塞的,直到GC完成。

除此之外,GC的性能又得到了进一步提升,尤其是在拥有庞大(大于50GB)的堆内存的情况下。

单调的时间度量

在Go1.9之前,标准库代码包time中的一些函数是通过读取计算机的系统时钟来实现功能的。这样做的好处是总与系统保持一致,而坏处是一旦系统时钟被篡改,它们也会无脑地跟着变更。这可能会让使用它们的Go程序产生错误,尤其是我们在依赖时间做一些任务的时候。请想象一下,千年虫或者闰秒调整那样的问题再次出现时会怎样。因此,Go必须优雅地应对系统时钟重置。这在Go1.9中得到了解决。

现在,包括runtime、time、context和net在内的代码包都使用了单调的时间度量。在必要时,系统时钟重置导致的时间度量错误会被自动修正。我们不会再为此困扰了。

除了上述的这些可喜的改进之后,Go标准库也被大范围地更新了。比如,新的用于处理位操作的math/bits包。又比如,testing包对帮助函数更加友好。还比如,runtime/pprof包中的诸多改进。等等等等。

在开始撰写本文之前,Go1.9.2早已发布。在我的Go程序研发团队中有个规定:通常情况下,当小版本(比如1.9)的第二个维护版本出来之后,我们就开始更新各种测试环境甚至生产环境上Go的版本。当然,我们的评估和试用在小版本的第一个版本发布时就开始了。这既可以避免因过于激进而踩坑,又可以尽早享用Go版本升级的红利。在这里供大家参考。

展望

Go1.10+

先说说离我们最近的Go1.10。该版本的beta版已经发布。它又带来了众多改进,比如:更宽松的语法、更智能的标准命令、更完善的Go汇编支持、无限的最大P数量(GOMAXPROCS)设置,以及一如既往的性能提升和标准库改进。

注意,有两个标准库代码包的变更可能会导致你代码的必要修改。一个是bytes包,涉及到Fields、FieldsFunc、Split和SplitAfter函数。另一个是net/url包,主要涉及到ResolveReference函数。后者不会再想当然地去掉URL路径中的“多余”斜杠。这样可以避免http.Client在某些情况下的重定向错误。同时也是为了符合RFC3986协议。

至于以上变更的细节,请大家参看Go1.10的releasenotes。

在Go即将满10岁之际,在Gophercon2017大会上,Go团队终于把Go2的事情郑重地摆上了桌面。Go语言的目标一直是帮助开发者们高效地完成现代软件的开发和部署。Go2的目标仍然如此。但更重要的是,Go2会修正Go1中不利于实现前述目标的一些方面。据我个人推测,Go2的升级方式会介于Java和Python之间,同时在广大开发者可接受的范围内会更倾向于Python。大家都知道,Java的大版本升级过于保守,而Python的大版本升级则过于激进。

Go2可能会出现很大的变革,但就像Java、Python这些“白胡子”语言一样,大版本的升级必然会存在很多权衡和妥协,也必然会包含与Go1.x的不兼容。到目前为止,Go团队还没有公布任何细节。改革错误处理方式?增加不可变值?自定义泛型?一切都没有定数。但不论怎样,Go团队会想尽一切办法让广大开发者平滑过渡到Go2之上。让我们翘首期盼吧!

社区,永远的社区

国内使用Go语言的人依然在不断增长。其增长程度已经让一些在线教育公司敏锐地嗅到了机会。他们已经把发展Go语言相关课程作为了2018年的重点之一。

2017年我对国内社区的最大感触就是地方性的Go语言组织越来越多了。许多省市内的gopher们都自行组织起来,一起加强交流、共同增进技能。就拿12月16日在深圳举办的meetup来说,当场人数也超过了100。现场的技术氛围也很浓郁。大家都在积极地互通有无。可喜可贺!现在各地方的中小型Go语言技术聚会基本上都可以达到这个规模了。今年我在北京只组织了一场活动,有些遗憾。我在这里也检讨一下。

我去年呼吁各个使用Go语言的公司在Github上Go语言的wiki(https://github.com/golang/go/wiki/GoUsers)中加入自己公司的主页链接。那时,China那一栏下只有一家公司。如今增加到了5家。可是这仍然比我知道的公司少太多了。因此我在今年再呼吁一下。大家多向官方以及国际发声吧!这对我们是有好处的。

另外,我很久之前在Github上建立了一个国内卓越Go项目列表(https://github.com/GoHackers/awesome-go-China),也希望大家能把个人或者公司开源的项目加入其中。如此一来,我们就可以在进行Go框架和工具选型的时候有一个比较集中、方便的参考之地。同时也可以增进大家的交流。我们可以在Github上更有的放矢地为优秀项目贡献代码。另一方面,这也是吸引代码贡献者的另一个渠道。

从人才市场的方面看,国内招聘Go工程师的公司也越来越多了。就算在年底,各大Go语言微信群和QQ群里也能看到很多招聘启事。看来明年又是Go工程师们大展拳脚的一年。

我个人认为Go语言在当前以及可见的未来会更多的应用在如下几个热门领域中。首当其冲的当然是云计算。Go不仅擅长Web系统、API服务等应用层软件的开发,也可以用来开发中间件甚至基础设施。云计算中几乎所有的软件都可以用Go语言来开发,而且很有优势。实际上,在这个领域,可以说Go语言已经成为大家的首选语言了。这部分得益于docker、kubernetes等项目的持续火爆。其次是区块链。现在国内做区块链的公司有很多。这些公司也大都以Go语言为主力。虽然比特币(或者说加密数字货币)现在在国内的状态不容乐观。但是作为其核心技术的区块链却依然受到热捧。据我所知,很多Go工程师投身其中了,甚至有些国外的区块链创业公司已想在国内开设办公室了。

顺便说一句,对区块链技术有兴趣的gopher可以去研究下hyperledger(https://cn.hyperledger.org/)这个项目。再次是数据科学和机器学习这两个领域。很多人会说“这都是Python的底盘啊,而且几乎不可动摇”。但是我要说的是,其实在这两个领域中Python也不是一家独大的,像R、Julia、MATLAB、C++等语言都有自己的一席之地。对标numpy,最近持续升温的Gonum(https://github.com/gonum/gonum)很不错。另外,Tensorflow框架也早早放出了Go语言版本的API。当然我不是鼓吹大家在这些领域中使用Go语言。各个语言在不同领域都有各自的优势,重点是怎样提高生产力。但是我认为Go语言会在这些领域持续渗透并扩大优势。各位可以重点关注一下。

在这个面临着技术和商业大变革的时代,Go语言是一项很好的技术资本,起码你应该让它作为你技术工具箱中的重要一员。如果你主攻后端技术但还不会Go语言,我建议你花几个小时入门一下,然后再花一些时间体会和理解它的编程哲学。我相信这对你的技术成长是很有好处的。

最后,如果你想融入一个国内的Go语言技术社区,不妨加入我发起的组织——GoHackers。它的前身是Go语言北京用户组(微信公众号:golang-beijing)。后者已经有3年左右的历史了。GoHackers的微信公众号是“gohackers”,在开发者头条中的团队号是“GoHackers”。前者主要用于发布Go语言动向、国内活动预告以及相关招聘启事,后者主要用来分享优秀的国内外Go技术文章。

配套repo:https://github.com/hyper0x/review2017-go

参考文献:

[1]Go1.8isreleased:https://blog.golang.org/go1.8

[2]Go1.9isreleased:https://blog.golang.org/go1.9

[3]Go1.10ReleaseNotes(DRAFT):https://tip.golang.org/doc/go1.10

[4]TowardGo2:https://blog.golang.org/toward-go2

[5]EightyearsofGo:https://blog.golang.org/8years

[6]TypeAliases(Proposal):https://github.com/golang/proposal/blob/master/design/18130-type-alias.md

[7]AnOverviewofsync.Map:https://github.com/gophercon/2017-talks/blob/master/lightningtalks/BryanCMills-AnOverviewOfSyncMap/An%20Overview%20of%20sync.Map.pdf

[8]MonotonicElapsedTimeMeasurementsinGo(Proposal):https://github.com/golang/proposal/blob/master/design/12914-monotonic.md

[9]Diagnostics:https://tip.golang.org/doc/diagnostics.html

[10]CodebaseRefactoring(withhelpfromGo):https://talks.golang.org/2016/refactor.article

作者介绍

郝林,从业近13年的互联网软件开发者。《Go并发编程实战》的作者,并于2017年4月出版了第2版。技术社区GoHackers和AIHackers的发起人和组织者。微服务、数据挖掘和DevOps技术的积极实践者。现在某中型电商创业公司任技术总监。

今日荐文

点击下方图片即可阅读

Web开发这十年

OK,关于下载网站源码分享原包和app下载网站源码的内容到此结束了,希望对大家有所帮助。

Published by

风君子

独自遨游何稽首 揭天掀地慰生平