上周笔者的博客《为什么大厂上在用GO语言-读透GO语言的切片》立了个flag,要带大家共同来实战一下GO语言的项目,通过这些日子的研究对于GO语言笔者已经路转粉了。
笔者在接触GO之前长期从事物联网操作系统的内核及时序数据库的开发工作,特别推崇开源精神,曾经参与国产开源数据库Tdengine与TencentOS等开源项目,在CSDN公众号中发表过《腾讯开源开年红!TencentOS 内核正式开源》、《腾讯Tiny OS 组合 NB-IoT,值得程序员一试吗?》、《Iot数据库风口已至》等文章。
2019年来自中国的项目和人员为Github社区的贡献度均排名世界第二,而且来自中国的开源大潮还在不断延续,据最新的资料显示目前Github上最新注册的开发者中有40%以上来自于中国,甚至有外国的开发者吐槽说Github趋势榜已经被中文项目所霸占了,可以说开源已经成为了我国文化输出的一张名片,让世界上其它国家开始学习“中国话”。
在十年前业界普遍流传着一句话叫做“代码正在吞没世界”,后来又说“互联网世界的一切源自开源”,而直到最近人们才真正醒悟原来云原生才是背后的那个大BOSS,而GO语言恰恰是开源+云原生的基石。凡是不使用云的都将落后,都无法做到敏捷,跟不上时代。
虽然我国在很多基础软件的研发领域取得了不少的成果,比如操作系统中的TencentOS系列、Ali Things OS、Rtthread、鸿蒙OS等等,在芯片方面阿里也有无剑100-MCU开源芯片平台发布,但是在编程语言方面我们还始终没有什么值得一提的进展,因此笔者开始关注到了GO语言。在阅读了GO语言的代码之后,可以肯定的讲在设计思想上GO语言有很多值得我国学习之处。
Go语言(Github地址:https://github.com/golang/go)是在2007年9月由Rob ert Gries emer, Rob Pike和Ken Thomps共同在谷歌大会上构想出来的,据说时任Google首席软件工程师的Rob Pike实在无法忍耐C++缓慢的编译速度,因此他和Robert Griesemer探讨了程序设计语言的问题,他们认为,编程语言的简约化比增加新特性更加重要。随后说服了C语言之父Ken Thompson,并共同发起了Golang项目,做为新型语言的实验项目。
GO语言可以说是和容器相生相伴的,大名鼎鼎的Docker,完全用GO实现,业界火爆的容器管理系统kubernetes(k8s),完全用GO实现,最新的Docker Swarm,完全用GO实现。而GO语言做为容器的支柱,使云计算变得越来越标准化,基于GO语言的容器通过其在并发、性能等方面的特性使得开发者只需关注自己的业务逻辑,而无需显示地保有计算资源,免去资源管理、系统运维等工作。
其实这个题目并不难,具体代码及注释如下:
package main
import ( //
"fmt" // fmt 包使用函数实现 I/O 格式化(类似于 C 的 printf 和 scanf 的函数), 格式化参数源自C,但更简单
"io/ioutil"
//"sync"
//"time"
)
func PrintRepreatFile(path string, fileNameSizeMap map[string]int64, exFileList []string) {
fs, _ := ioutil.ReadDir(path)
for _, file := range fs {
if file.IsDir() {
PrintRepreatFile(path+"/"+file.Name(), fileNameSizeMap, exFileList)//遍历整个文件系统,如果是目录则递归调用
} else {
if file.Size() > 1000000 {//设定文件清理阈值,如果大于一定大小再进行清理
fileSize := fileNameSizeMap[file.Name()]//通过查哈希表的方式来确定,有无重名且大小相同的文件。
if fileSize == file.Size() {
fmt.Println(path + "/" + file.Name())//如果有则打印出来
exFileList = append(exFileList, path+file.Name())//将结果记入切片当中
} else {
fileNameSizeMap[file.Name()] = file.Size()
}
}
}
}
}
func main() {
//方式一
fileNameSizeMap := make(map[string]int64, 10000)
exFileList := make([]string, 100, 1000)
PrintRepreatFile("E:/test", fileNameSizeMap, exFileList)
}
这个程序在GO语言的环境下可以直接运行使用,其中有几个知识点,也是咱们前文提到过的,首先是切片的大小一定要设定的相对合适一些,如果容量不够大造成频繁扩容非常浪费资源。二是哈希表也就是map没有并发安全的属于,在我们这个未引入并发的程序中可以使用,如果有并发操作,那么map不再适用了。
可能很多人和笔者一样,也是被GO语言的在并发性能所吸引入坑的,GO语言之父也就是UNIX之父Ken Thompson明显给出了很多建议,根据笔者在操作系统方面的相关经验来看,GO语言设计中经常参考UNIX内核的设计思路。比如硬定时器的数量有限,无法满足系统实际运行需要,所以在内核代码中就会看到基于硬件定时器的软件定时器的方案,而软件定时器的数量可以比硬件定时器多几百倍。
这样的理念明显融合到了 goroutine之中,由于其它编程语言往往直接通过系统级别的线程来实现并发功能,但是这样的方式往往会是大马拉小车,造成系统资源的浪费。因此GO语言封装了所有的系统操作,实现了更加轻量级的协程-goroutine。只要使用关键字(go)就可以启动协程,对比C++、JAVA的多线程并发模型,GO的协程更简单明了。
当然协程之间的消息通信与并发控制也是非常重要的一环。在GO语言借鉴了Message Queue的消息队列机制替代共享内存的方式进行协程间通信,其中管道channel作为基本的数据类型,保证并发时的操作安全。而且管道的引入还带来很多实践中非常实用的功能,比如可以方便实现生产者、消费者等并发设计模式,而这些设计模式在其它使用共享存内存的并发模型中实现起相关功能来非常的繁锁。
在GO语言中在调用函数前加入go 关键字,就能启动一个协程,也就是一个并发,但是我们上面的程序如果把调用方式改为:
go PrintRepreatFile("E:/test", fileNameSizeMap, exFileList)
你会发现程序会直接退出,什么都没做,所以GO语言的并发对于初学者来说还是有一定门槛的,比如上例中如果想设计成一个并行的程序,如何让多个协程共同来帮忙找出重复的文件其实还是要费一番周折的。笔者接下来的几次博客,计划以这个找到重复文件为题,带大家共同来走进GO语言并发的世界。