陈星星

陈星星 查看完整档案

深圳编辑  |  填写毕业院校某某娱乐  |  高级后端开发工程师 编辑 github.com/hunterhug 编辑
编辑

谜一样的烟火🎆

个人动态

陈星星 发布了文章 · 5月28日

数据结构和算法(Golang实现)(30)查找算法-2-3-4树和普通红黑树

2-3-4树和普通红黑树

某些教程不区分普通红黑树和左倾红黑树的区别,直接将左倾红黑树拿来教学,并且称其为红黑树,因为左倾红黑树与普通的红黑树相比,实现起来较为简单,容易教学。在这里,我们区分开左倾红黑树和普通红黑树。

红黑树是一种近似平衡的二叉查找树,从2-3树或2-3-4树衍生而来。通过对二叉树节点进行染色,染色为红或黑节点,来模仿2-3树或2-3-4树的3节点和4节点,从而让树的高度减小。2-3-4树对照实现的红黑树是普通的红黑树,而2-3树对照实现的红黑树是一种变种,称为左倾红黑树,其更容易实现。

使用平衡树数据结构,可以提高查找元素的速度,我们在本章介绍2-3-4树,再用二叉树形式来实现2-3-4树,也就是普通的红黑树。

三、应用场景

红黑树可以用来作为字典Map的基础数据结构,可以存储键值对,然后通过一个键,可以快速找到键对应的值,相比哈希表查找,不需要占用额外的空间。我们以上的代码实现只有value,没有key:value,可以简单改造实现字典。

Java语言基础类库中的HashMapTreeSetTreeMap都有使用到,C++语言的STL标准模板库中,mapset类也有使用到。很多中间件也有使用到,比如Nginx,但Golang语言标准库并没有它。

系列文章入口

我是陈星星,欢迎阅读我亲自写的 数据结构和算法(Golang实现),文章首发于 阅读更友好的GitBook

查看原文

赞 3 收藏 1 评论 0

陈星星 回答了问题 · 4月12日

goalng map delete操作不会释放底层内存

go version go1.13.1 darwin/amd64

尝试做一次实验:

package main

import (
    "fmt"
    "runtime"
)

//var a = make(map[int]struct{})

func main() {
    v := struct{}{}

    a := make(map[int]struct{})

    for i := 0; i < 10000; i++ {
        a[i] = v
    }

    runtime.GC()
    printMemStats("添加1万个键值对后")
    fmt.Println("删除前Map长度:", len(a))

    for i := 0; i < 10000-1; i++ {
        delete(a, i)
    }
    fmt.Println("删除后Map长度:", len(a))

    // 再次进行手动GC回收
    runtime.GC()
    printMemStats("删除1万个键值对后")

    // 设置为nil进行回收
    a = nil
    runtime.GC()
    printMemStats("设置为nil后")
}

func printMemStats(mag string) {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    fmt.Printf("%v:分配的内存 = %vKB, GC的次数 = %v\n", mag, m.Alloc/1024, m.NumGC)
}

输出:

添加1万个键值对后:分配的内存 = 241KB, GC的次数 = 1
删除前Map长度: 10000
删除后Map长度: 1
删除1万个键值对后:分配的内存 = 65KB, GC的次数 = 2
设置为nil后:分配的内存 = 65KB, GC的次数 = 3

针对:底层内存不会真正的释放, 这样可能会导致内存一直增长下去造成问题。

可以看到,新版本的 Golang 难道真的会回收 map 的多余空间哦,难道哈希表会随着 map 里面的元素变少,然后缩小了?

我又尝试了一下,将 map 放在外层:

package main

import (
    "fmt"
    "runtime"
)

var a = make(map[int]struct{})

func main() {
    v := struct{}{}

    //a := make(map[int]struct{})

    for i := 0; i < 10000; i++ {
        a[i] = v
    }

    runtime.GC()
    printMemStats("添加1万个键值对后")
    fmt.Println("删除前Map长度:", len(a))

    for i := 0; i < 10000-1; i++ {
        delete(a, i)
    }
    fmt.Println("删除后Map长度:", len(a))

    // 再次进行手动GC回收
    runtime.GC()
    printMemStats("删除1万个键值对后")

    // 设置为nil进行回收
    a = nil
    runtime.GC()
    printMemStats("设置为nil后")
}

func printMemStats(mag string) {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    fmt.Printf("%v:分配的内存 = %vKB, GC的次数 = %v\n", mag, m.Alloc/1024, m.NumGC)
}

输出:

添加1万个键值对后:分配的内存 = 243KB, GC的次数 = 1
删除前Map长度: 10000
删除后Map长度: 1
删除1万个键值对后:分配的内存 = 244KB, GC的次数 = 2
设置为nil后:分配的内存 = 67KB, GC的次数 = 3

这时 map 好像内存没变化,直到设置为 nil。

为什么全局变量就会不变呢?

而为什么,局部变量还在使用着,它里面还剩一个元素,为什么就会缩小呢,大家都是 map,空间会一直增长,局部变量有优先权变小?难道 Golang 底层做了一些特殊处理?

于是我又做了一次操作,将局部变量添加一万个数,然后再删除9999个数,再添加9999个,看其变化:

package main

import (
    "fmt"
    "runtime"
)

//var a = make(map[int]struct{})

func main() {
    v := struct{}{}

    a := make(map[int]struct{})

    for i := 0; i < 10000; i++ {
        a[i] = v
    }

    runtime.GC()
    printMemStats("添加1万个键值对后")
    fmt.Println("删除前Map长度:", len(a))

    for i := 0; i < 10000-1; i++ {
        delete(a, i)
    }
    fmt.Println("删除后Map长度:", len(a))

    // 再次进行手动GC回收
    runtime.GC()
    printMemStats("删除1万个键值对后")

    for i := 0; i < 10000-1; i++ {
        a[i] = v
    }

    // 再次进行手动GC回收
    runtime.GC()
    printMemStats("再一次添加1万个键值对后")

    // 设置为nil进行回收
    a = nil
    runtime.GC()
    printMemStats("设置为nil后")
}

func printMemStats(mag string) {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    fmt.Printf("%v:分配的内存 = %vKB, GC的次数 = %v\n", mag, m.Alloc/1024, m.NumGC)
}

输出:

添加1万个键值对后:分配的内存 = 242KB, GC的次数 = 1
删除前Map长度: 10000
删除后Map长度: 1
删除1万个键值对后:分配的内存 = 243KB, GC的次数 = 2
再一次添加1万个键值对后:分配的内存 = 65KB, GC的次数 = 3
设置为nil后:分配的内存 = 65KB, GC的次数 = 4

这次局部变量删除后,和全局变量map一样了,内存耶没变化。

但是添加10000个数后内存反而变小了。

这神奇的 Golang 啊。

map删除元素后map内存是不会释放的,无论是局部还是全局,但引出了上面一个奇怪的问题。

https://github.com/golang/go/...

为什么添加10000个数后内存反而变小了?因为 Golang 编译器有提前优化功能,它知道后面 map a 已经不会被使用了,所以会垃圾回收掉,a = nil 不起作用。

关注 5 回答 3

陈星星 赞了文章 · 4月7日

Rancher 2.4正式发布!打造业界规模最大的云边协同集群

Rancher 2.4全新特性一览:边缘集群部署场景的延展性、零宕机升级集群、安全性增强以及全新的托管选项,为用户提供从数据中心到云端到边缘的海量Kubernetes集群一站式管理体验

2020年4月1日,业界应用最为广泛的Kubernetes管理平台创建者Rancher Labs(以下简称Rancher)宣布Rancher 2.4正式GA,Kubernetes现已成为从数据中心到云端到边缘的通用计算平台引擎,为了帮助企业最大化资源利用效率,Rancher 2.4为企业提供了在任意基础设施上运行Kubernetes所需的运行效率、伸缩性和安全性。

“许多企业选择Rancher,是因为Rancher是业界唯一能够满足其异构基础设施的多云多集群Kubernetes管理平台。”Rancher联合创始人及CEO梁胜表示:“Rancher 2.4是Rancher一贯以来推行‘Run Kubernetes Everywhere’战略的有力佐证和全新动力,它在伸缩性、复杂性和开放性方面,与市面上现存的替代解决方案拉开了巨大的差距。”

将Kubernetes集群从云端规模扩展至边缘规模

全球领先的信息技术研究和顾问公司Gartner的一项调查显示,到2022年,将有75%的企业在数据中心和云端之外创建和处理企业数据。无论企业是希望通过5G向数以百万计的移动用户提供海量内容,还是运行低功耗、远程风力装置的网络,还是向具有数千个分支机构的零售店提供软件更新,抑或是管理大量具有面部或物体识别功能的高安全性摄像头,边缘场景将会驱动Kubernetes集群部署的大规模增长。

通过Rancher 2.4,Rancher提供了在极具规模的边缘场景中支持Kubernetes所需的伸缩性、管理能力和安全功能。 Rancher 2.4主要增强的功能包括:

  • 海量集群管理:此次Rancher 2.4 GA版本支持2000个集群和10万个节点。更值得一提的是,Rancher 2.4的产品架构已得到增强,在即将发布的下一个新版本中将提供支持100万个集群的途径;
  • 网络条件有限时的极佳选择:对于不具备固定或稳定网络连接的集群而言,Rancher 2.4 + k3s的搭配,是管理、升级和修补集群的最佳选择。Rancher 2.4可以远程启动升级,但升级过程将在本地k3s集群上进行管理,从而允许用户在本地管理升级和修补集群,然后在恢复网络连接后与管理服务器同步;
  • RKE支持零宕机升级集群:随着越来越多的组织在生产环境中使用Kubernetes运行承载着企业关键业务的应用程序,他们对采用不间断的方式维护其Kubernetes基础设施有着越来越强烈的诉求。Rancher 2.4可支持零宕机升级集群,它允许组织在不中断应用程序的情况下升级Kubernetes集群和节点。除此之外,用户可以选择并配置其加载项和升级策略,以使DNS和Ingress不会受到服务中断的影响;
  • 通过CIS基准扫描增强安全性:集群安全性对于成功的Kubernetes策略至关重要。然而,AimPoint的最新研究显示,44%的公司正是由于对Kubernetes容器安全的顾虑,才推迟了将容器化应用程序用于生产环境中。为了帮企业解决这一难题,Rancher 2.4创造性地引入了CIS安全扫描,用户可以根据互联网安全中心发布的100多个CIS安全基准,结合Kubernetes集群安全的实际定义,对RKE集群进行点对点(ac-hoc)安全扫描。用户可以创建自定义测试配置,设置扫描完成/扫描失败的通知告警,并根据这些信息采取纠正措施,以确保这些集群满足所有的安全要求。

Rancher托管服务

许多组织可能缺少或者不愿意花费部署和管理本地Rancher Server所需的基础架构、计算和人力资源。为了响应用户的这一需求,自Rancher 2.4开始,我们提供Rancher托管服务,每个客户都可以获得一个Rancher Server管理控制计划的专用AWS实例。Rancher托管服务是功能齐全的Rancher Server,提供99.9%的SLA,以及无压力的升级、安全补丁和备份。GKE、AKS等下游集群不包含在SLA中,而是继续由相应的K8S发行版供应商运营。在国产化及本土化运营方面,Rancher也将针对中国区客户,推出部署在阿里云上的Rancher托管服务。

独特的战略推动公司发展

通过运行相同的专有堆栈来提供多云支持是普遍可见的解决方案,而Rancher则有别于此。Rancher为包括RKE、k3s、微软云AKS、亚马逊EKS、谷歌云GKE、阿里云ACK、腾讯云TKE、百度智能云CCE和华为云CCE在内的所有认证发行版提供了直观极简且具备一致的Kubernetes管理体验,无论这些Kubernetes运行在数据中心、云端或者是边缘。

Rancher CMO Peter Smails强调:“那些强迫客户在所有环境中运行专有的Kubernetes堆栈的解决方案,与支持异构的Kubernetes最佳发行版Rancher之间,存在巨大的理念鸿沟。无论是现在还是将来,客户都希望拥有Rancher所带来的灵活性、自由度和成本优势。”

Rancher独特的公司定位策略引起了客户和投资人的共鸣。2019年,Rancher全年营收同比增长169%,并于近日正式宣布获得了4000万美元D轮融资。据透露,本轮融资用于进一步推动产品的持续创新,加大对Go-To-Market的投入,以及拓宽Kubernetes在全新市场与行业中的无限可能。

客户心声

目前,我们还没有一个标准化的分公司设置——我们需要针对不同的地点采用不同的组件。我们正在使用物理服务器,需要一名工程师来安装硬件、配置操作系统和安装应用程序,这可能需要长达5天的时间,成本约为2000美元。通过Rancher 2.4,k3s可以预装到服务器上,通过PEX远程启动,并自动连接到Rancher Kubernetes环境中进行远程管理。
——南非联合银行集团有限公司(ABSA Group Limited)容器平台管理负责人Zak Anderson
Rancher是我们所有Kubernetes集群的统一管理平台,因此,Rancher的可视化对于我们的系统配置安全性而言至关重要,将所有工具置于Rancher的前端和中心,将极大地提高我们大型、复杂和分布式体系架构的可视化和平台安全性。安全工具通常需要对我们的来历和去向有全面的了解,但Rancher完全消除了它的复杂性,并且使许多过程都实现了自动化。

——Optoro平台运营高级总监和首席信息安全官Zach Dunn

我们十分期待零宕机升级集群的功能。在先前的版本中,Rancher可以在升级过程中关闭所有节点。而Rancher
2.4是一个全新的里程碑,它将允许每个节点重新启动和升级。这是我们一直以来翘首以盼的功能。

——移动支付提供商DIMOCO的系统工程师Philipp-Michael Radl

About Rancher Labs

Rancher Labs由CloudStack之父梁胜创建。旗舰产品Rancher是一个开源的企业级Kubernetes管理平台,实现了Kubernetes集群在混合云+本地数据中心的集中部署与管理。Rancher一向因操作体验的直观、极简备受用户青睐,被Forrester评为2018年全球容器管理平台领导厂商,被Gartner评为2017年全球最酷的云基础设施供应商。

目前Rancher在全球拥有超过三亿的核心镜像下载量,并拥有包括中国人寿、华为、中国平安、兴业银行、民生银行、平安证券、海航科技、厦门航空、上汽集团、海尔、米其林、丰田、本田、中船重工、中联重科、迪斯尼、IBM、Cisco、Nvidia、辉瑞制药、西门子、CCTV、中国联通等全球著名企业在内的共40000家企业客户。

查看原文

赞 1 收藏 0 评论 2

陈星星 回答了问题 · 4月7日

解决B-/B+树的平衡到底指的是什么

B树和B+树是多路平衡树的简称,主要用在大量数据的内存存储结构。

区别是B+树的数据在叶子节点且连起来,方便select * from table order by xx。关系型数据库经常要查一堆数据,B+树就用上了。

而B树在Mongo有用到,因为数据关联性不是很强,所以数据不需要在叶子上。

B树和B+树的平衡是严格平衡,非常严格,所有节点要么都有儿子,要么都没有儿子,且根节点到所有的叶子节点的路径是一样长的。

你可以认为是满的多路树,满的话,看起来就是一个全金字塔形。

关注 4 回答 3

陈星星 赞了文章 · 4月7日

为什么要用Go语言?

本文章创作于2020年4月,大约6000字,预计阅读时间15分钟,请坐和放宽。

logo.png

前言

Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易[1]。

Go 语言被设计成一门应用于搭载 Web 服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。对于高性能分布式系统领域而言,Go语言无疑比大多数其它语言有着更高的开发效率。它提供了海量并行的支持,这对于游戏服务端的开发而言是再好不过了[1]。

其实早在2018年前,我就已经有在国内的程序员环境中断断续续地听到Go语言的消息,Go语言提供的方便的并发编程方式,十分适合我当时选择的毕业设计选题,但是受限于导师的语言选择、项目的进度追赶、考研的时间压榨,一直没有机会来好好地学习这门语言。

在进入研究生阶段后,尽管研究的方向和算法相关,但未来的职业方向还是选择了以后端为主,主要是因为想做更多和业务相关的工作。为了能在有限的时间里给予自己足够深的知识底蕴,选择了一些让自己去深入了解的方向,Go语言自然也在其中,今天终于有机会来开始研究这门语言。

为什么要用Go语言?

撰写此文的初衷,是本文的标题,也是我作为初学者一直以来的疑问:

“我为什么要用Go语言?”

为了回答这个问题,我翻阅了很多Go语言相关的文档、书籍和教程,我发现我很难在它们之中找到非常明显直接的答案,书上和教程只会说,“是的,Go语言好用”

对于部分人来说,这个问题的答案或许很“明显”,比如选择Go语言是因为Google设计的语言、Go开发赚的钱多、XX公司使用Go语言等等,如果想要了解这门语言更加本质的东西,仅仅这些答案我认为是还不够的。

部分Go的教徒可能会说,他们选择的理由是和语言本身相关的,比如:

  • Go编译快
  • Go执行快
  • Go并发编程方便
  • Go有垃圾回收(Garbage Collection, GC)

的确,Go是有这些特点,但这并非都是Go独有的

  • 运行时解释的脚本语言(比如Python)几乎不需要时间编译
  • C、C++甚至是汇编,基本上能够榨干一台机器的大部分性能
  • 大部分语言都有并发编程的支持库
  • 大部分语言都不需要程序员主动关注内存情况

一些Go的忠实粉丝把这种All in One的特性作为评价语言的标准,他们认为至少在这些方面,Go是可以完美的代替其他语言的。

那么,Go真的能优秀到完全替代另一个语言么?

其实未必,我始终认为银弹是不存在的[2],无论是在这次调查前,还是在这次调查后。

本文从Go语言被设计的初衷出发,深入互联网各种角落,调查Go所具有的那些特性是否足够优秀,同时和其他语言进行适当的比较,你可以选择性的阅读、接受或者反对我的内容,毕竟有交流才能传播知识。

我的最终目的是让更多的初学者看到Go没有轻易暴露出的缺点,同时也能看到Go真正优秀的地方

设计Go的初衷

Go语言的主要目标是将静态语言的安全性和高效性与动态语言的易开发性进行有机结合,达到完美平衡,从而使编程变得更加有乐趣,而不是在艰难抉择中痛苦前行[3]。

Google公司不可能无缘无故地设计一个新语言(一些特性相比于其他语言也没有新到哪里去),这一切肯定是有原因的。

设计Go语言是为了解决当时Google开发遇到的一些问题[4]:

  • C++编译慢、没有现代化(入门级友好的)的内存管理
  • 数以万计行的代码,难以维护
  • 部署的平台各式各样,交叉编译困难
  • ......

joke.png

找不到什么合适的语言,想着反正都是弄来自己用,Google选择造个轮子试试。

Go 语言起源 2007 年,并于 2009 年正式对外发布。它从 2009 年 9 月 21 日开始作为谷歌公司 20%兼职项目,即相关员工利用 20% 的空余时间来参与 Go 语言的研发工作。该项目的三位领导者均是著名的 IT 工程师:Robert Griesemer,参与开发 Java HotSpot 虚拟机;Rob Pike,Go 语言项目总负责人,贝尔实验室 Unix 团队成员,参与的项目包括 Plan 9,Inferno 操作系统和 Limbo 编程语言;Ken Thompson,贝尔实验室 Unix 团队成员,C 语言、Unix 和 Plan 9 的创始人之一,与 Rob Pike 共同开发了 UTF-8 字符集规范。自 2008 年 1 月起,Ken Thompson 就开始研发一款以 C 语言为目标结果的编译器来拓展 Go 语言的设计思想[3]。

go-designers.png

Go 语言设计者:Griesemer、Thompson 和 Pike [3]

当时Google的很多工程师是用的都是C/C++,所以语法的设计上接近于C,Go的设计师们想要解决其他语言使用中的缺点,但是仍保留他们的优点[5]:

  • 静态类型和运行时效率
  • 可读性和易用性
  • 高性能的网络和多进程
  • ...

emmm,这些听起来还是比较玄乎,毕竟设计归设计,实现归实现,我们回顾一下现在Go的几个主要特点,编译速度、执行速度、内存管理以及并发编程。

Go的编译为什么快

当然,设计Go语言也不是完全从零开始,最初Go的团队尝试设计实现一个Go语言的编译前端,由基于C的gcc编译器来编译成机器代码,这个面向gcc的前端编译器也就是目前的Go编译器之一的gccgo。

与其说Go的编译为什么快,不如先说说C++的编译为什么慢,C++也可以用gcc编译,编译速度的大部分差异很有可能来源于语言设计本身。

在讨论问题之前,其中需要先说明的一点是:这里比较的编译速度都是在静态编译下的

静态编译和动态编译的区别:

  • 静态编译:编译器在编译可执行文件时,要把使用到的链接库提取出来,链接打包进可执行文件中,编译结果只有一个可执行文件。
  • 动态编译:可执行文件需要附带独立的库文件,不打包库到可执行文件中,减少可执行文件体积,在执行的时候再调用库即可。

两种方式有各自的优点和缺点,前者不需要去管理不同版本库的兼容性问题,后者可以减少内存和存储的占用(因为可以让不同程序共享同一个库),两种方式孰优孰弱,要对应到具体的工程问题上,Go默认的编译方式是静态编译

回到我们要讨论的问题:C++的编译为什么慢?

C++编译慢的主要两个大头原因[6]

  • 头文件的include方式
  • 模板的编译

C++使用include方式引用头文件,会让需要编译的代码有乘数级的增加,例如当同一个头文件被同一个项目下的N个文件include时,编译器会将头文件引入到每一份代码中,所以同一个头文件会被编译N次(这在大多数时候都是不必要的);C++使用的模板是为了支持泛型编程,在编写对不同类型的泛型函数时,可以提供很大的便利,但是这对于编译器来说,会增加非常多不必要的编译负担。

当然C++对这两个问题有很多后续的优化方法,但是这对于很多开发者来说,他们不想在这上面有过多时间和精力开销。

大部分后来的编程语言在引入文件的方式上,使用了import module来代替include 头文件的方式,import解决了重复编译的问题,当然Go也是使用的import方式;在模板的编译问题上,由于Go在设计理念上遵循从简入手,所以没有将泛函编程纳入到设计框架中,所以天生的没有模版编译带来的时间开销(没有泛型支持也是很多人不满Go语言的理由)。

在Go 的1.5 版本中,Go团队使用Go语言来编写Go语言的编译器(也叫自举),相比于gccgo来说:

  • 提高了编译速度,但执行速度略有下降(性能细节优化还不如gcc)
  • 增加了可编译的平台类型(以往受限于gcc)

在此之外,Go语言语法中的关键字也是非常少的(Go1.11版本里只有25个)[7],这也可以减少编译器花费在语法解析上的时间开销。

keywords.png

所以在我看来,Go编译速度快,主要出于四个原因

  • 使用了import的引用管理方式;
  • 没有模板的编译负担;
  • 1.5版本后的自举编译器优化;
  • 更少的关键字。

所以为了加快编译速度、放弃C++而转入Go的同时,也要考虑一下是否要放弃泛型编程的优点。

注:泛型可能在Go 2版本获得支持。

Go的实际性能如何

Go的执行速度,可以参考一个语言性能测试数据网站 —— The Computer Language Benchmarks Game[8]。

这个网站在不同的算法上对每个语言进行测试,然后给出时间和内存上的开销数据比对。

比较的语言有C++、Java、Python。

首先是时间开销:

time-cost.png

注意时间开销的单位是s,并且Y轴为了方便进行不同跨度上的比较,所以选取的是对数轴(即非线性轴,为1-10-100-1000的比较跨度)。

然后是内存开销:

mem-cost.png

注意Y轴为了方便进行不同跨度上的比较,所以选取的是对数轴(即非线性轴,为1000-10000-100000-1000000的比较跨度)。

需要注意的是,语言本身的性能只决定了一个程序的最高理论性能,程序具体的性能还要取决于这个程序的实现方法,所以当各个语言的性能并没有太大的差异时,性能往往只取决于程序实现的方式。

通过两个图的数据可以分析:

  • Go虽然还无法达到C++那样的极致性能,但是在大部分情况下已经很接近了
  • Go和Java在算法的时间开销上难分伯仲,但在内存的开销上Java就要高得多了;
  • Go在上述的绝大部分情况下,至少时间和内存开销都比Python要优秀得多;

Go的并发编程

Go的并发之所以比较受欢迎,网络上的很多内容集中在几个方面:

  • 天生并发的设计
  • 轻量化的并发编程方式
  • 较高的并发性能
  • 轻量级线程Goroutines、并发通信Channels以及其他便捷的并发同步控制工具

由于Go在设计的时候就考虑到了并发的支持,或者说很多特性都是为了并发而设计,这和一些后期库支持并发和第三方库支持并发的语言不同。

所以Go的并发到底有多方便?在Go中使用并发,只需要在普通的函数执行前加上一个go关键字,就可以新建一个线程让函数在其中执行:

func main() {
    go loop() // 启动一个goroutine
    loop()
}

这样带来的好处不仅仅是让并发编程更方便了,在一些特定情况下,比如Go引用一些使用了并发的库时,这些库所使用的并发也是基于Go本身的并发设计,不会存在库使用另一套并发实现的情况,这样Go调度器在处理程序中的各种并发线程时,可以有更加统一化的管理方式。

不过Go的并发对于程序的实现要求还是比较高的,在使用一些通信Channel的场合,稍有疏忽就可能出现死锁的问题,比如:

fatal error: all goroutines are asleep - deadlock!

Go的并发量可以比大部分语言里普通的线程实现要高,这受益于轻量级的Goroutine,轻量化主要是它所占用的空间要小得多,例如64位环境下的JVM,它会默认固定为每个线程分配1MB的线程栈空间,而Goroutines大概只有4-8KB,之后再按需分配。足够轻量化的线程在相同的内存下也就可以有更高并发量(服务器CPU还没有饱和的情况下),同时也可以减少很多上下文切换的时间开销[9]。但是如果你的每个线程占用空间都非常大时(比如10MB,当然这是非常规需求的情况下),Go的轻量化优势就没有那么明显了。

Go在并发上的优点很明显,也是Go的功能目标,从语言设计上支持了并发,提供了统一便捷的工具,复杂的并发业务也需要在Go的一整套并发规范体系下进行编程,当然这肯定会牺牲部分实现自由度,但可以获得性能的提高和维护成本的下降。

PS:关于Go调度器的内容在这里并没有被提及,因为很难用简单的文字向读者说明该调度方式和其他调度方式的优劣,将在未来的某一篇中会细致地介绍Go调度器的内容。

Go的垃圾回收

垃圾回收(英语:Garbage Collection,缩写为GC),在计算机科学中是一种自动的存储器管理机制。当一个计算机上的动态存储器不再需要时,就应该予以释放,以让出存储器,这种存储器资源管理,称为垃圾回收。垃圾回收器可以让程序员减轻许多负担,也减少程序员犯错的机会[10]。

在使用Go或者其他支持GC的语言时,不用再像C++一样,手动地去释放不需要的变量占用的内容空间(free/delete)

的确,这很方便(对于懒人和容易忘记主动释放的人),但是也多了一些限制(暗箱操作的不透明性以及在GC处理上的性能开销)。GC也不是万能的,当遇到一些对性能要求较高的场景,还是需要记得进行一些主动释放或优化操作(比如说自定义内存池)。

PS:将在未来的某一篇中会细致地介绍Go垃圾回收的细节(如果你们也觉得有必要的话)。

什么时候可以选择Go?

Go有很多优点,编译快、性能好、天生并发以及垃圾回收,很多比较有特色的内容也还没有说到(比如gofmt)。

Go语言也有很多缺点,比如第三方库支持还不够多(相比于Python来说就少的太多了)、支持编译的平台还不够广、还有被称为噩梦的依赖版本管理(已经在改善了,但是还没有达到完全可靠的程度)。

所以到底Go适合做什么,不适合做什么?

分析了这么多后,这个问题其实很难回答,但我们可以选择先从不适合的领域把Go剔除掉,看看我们会剩下什么。

Go不适合做什么

  • 极致高性能优化的场景,你可能需要使用C/C++,甚至是汇编;
  • 简单流程的脚本工具、数值分析、深度学习,可能Python更适合(至少目前是);
  • 搭一个博客或网站,PHP何尝不是天下第一的语言呢;
  • 如果你想比较方便找到一份的后端工作,绝大部分公司的Java岗一直缺人(在实际生产过程中,目前Go仍没有比Java表现得好太多,至少没有好到让一个部门/公司将核心业务重新转向Go来进行重构);
  • ...

你可以找到类似上面那样的很多场景,你可能会发现Go并不能那么完美地替代掉谁。

Go适合做什么

最后,到了我们的终极问题,Go到底适合做什么?

读到这里你可能会觉得,好像是我把Go的特性吹了一遍,然后突然告诉你可能Go不适合你。

Go天生并发,面向并发,所以Go的定位一直很清楚,从最浅显的视角来看,至少Go作为一个有较高性能的并发后端来说,是具有非常大的诱惑力的。

尤其对于后端相关的程序员而言,在某些业务功能的初步实现上,简洁的语法、内置的并发、快速的编译,都可以让你更加高效快速地完成任务(前提是Go的内容足以完成你的任务),不用再去担忧编译优化和内存回收、不用担心过多的时间和内存开销、不用担心不同版本库之间的冲突(静态编译)以及不用担心交叉编译平台适配问题。

大部分情况下,编写一个服务,你只需要:实现、编译、部署、运行

高效快速,足够敏捷,这在企业的绝大部分项目的初期都是适用的,这也是大部分项目对开发初期的要求。当一个项目或者服务真的可以发展下去,需求的确触碰到Go的天花板时,再考虑使用更加好的语言或方法去优化也为时不晚。

简而言之,尽管Go的过于简洁带来了很多问题(有些人说的难听点叫过于简单),Go所具有的优点,可以让大部分人用编程语言这种工具,来解决对他们而言更加重要的问题。

Go语言不是银弹,但它的确能有效地解决这些问题。

参考文章

扩展阅读

在调查Go的过程中,发现了一些比较有意思、或者比较实用的文章,一并附在这里。

  • 我为什么选择使用 Go 语言?,该文写于2016年,在我的文章基本构思完成的时候,偶然看到了这篇文章,作者有很多早期Go版本的开发经验,里面有更多的细节都是出自于工程师的经验之谈,我发现其中的部分想法和我不谋而合,你可以把这篇文章当作本文的后续扩展阅读,不过要注意文章的时效,可能提及到的一些Go的缺点现在已经被改进了。
  • C/C++编译器的工作过程,主要是供不熟悉C系的朋友了解一下编译器的工作过程。
  • The Computer Language Benchmarks Game,一个对各个语言进行性能测试的网站,里面的算法具有一定的代表性,但是不能代表所有工程可能遇到的情况,仅供参考。
  • 为什么 Go 语言在某些方面的性能还不如 Java?,这是知乎上一个2017年开始有的问题,你可以看到很多人对于这个问题的分析,从多个角度来理解语言之间的性能差异。
  • go-wiki WhyGo,Go的Github仓库上维护的Wiki中,有一篇关于WhyGo的文章整理,不过大部分是英文,里面主要是很多关于“为什么我要选择Go”的软硬稿。
  • 为什么要使用Go语言,Go语言的优势在哪里,这个知乎的提问更早,是来自2013年的Yvonne YU用户,在Go的早期其实是具有很大的争议的,你可以看到大家在各个问题上的博弈。
  • 哪些公司在使用Go,Go的Github仓库上维护的Wiki中,有一篇关于全球都有哪些公司在使用Go,不过提供的信息大部分只有一个公司名,比如国内有阿里巴巴(而人家大部分都招Java),可以看看但参考性不大。
  • Go 语言的优点,缺点和令人厌恶的设计,这是Go语言中文网上一篇2018年的文章,如果你对语言本身的一些特性的设计感兴趣,你可以选择看看,作者从很多语法层面上介绍了Go的优点和缺点。
  • Ruby China - 瞎扯淡 真的没必要浪费心思在 Go 语言上,这是我无意中找到的一篇有名的帖子,这个问题始于2013年,在Ruby China上,其中也是大佬们(可能)从各个角度来辩论Go是否值得学习,可以当作武侠小说观看。
  • The way to Go - 3.8 Go性能说明,《The way to Go》这本书上为数不多关于Go性能问题的说明。
  • C++开发转向go开发是否是一个好的发展方向?,2014年知乎上关于C++和Go的一个讨论,其实我觉得“如果选择一个并不意味着就要放弃另一个”,程序员不是研究语言的,也不应该是只靠某一门语言吃饭。
  • 我为什么放弃Go语言 Liigo,嗯,2014年,仍旧是Go争议很大的时候,CSDN上一篇阅读数很高的文章,作者从自己的角度对Go进行批判(Go早期的确是有不少问题),你可以看到早期Go的很多问题,也可以斟酌这些问题对你是否重要以及到底在2020年的Go中有没有被解决。
  • Golang 本身是用什么语言写的?,一个关于编译的有趣的问题,可以适当了解。
  • 搞懂Go垃圾回收,一篇还算比较新的分析Go垃圾回收问题的文章。
  • 有趣的编程语言:Go 语言的启动时间是 C 语言的 300 多倍,C# 的关键字最多,这篇InfoQ文章其实算是一个典型的标题党,主要使用的是一个Github上关于各个语言HelloWorld程序启动时间的测试数据(https://github.com/bdrung/sta...,使用gccgo编译的Go程序的启动时间非常地长,的确是C的300多倍,但使用GC编译的Go程序启动时间只是C的2倍。
  • Go 语言的历史回顾,我一直在寻找一个整理Go的版本变动细节的文章,在Go的官方文档和各种书籍上寻找无果时,在InfoQ上找到了一篇还算跟踪地比较新的(Go 1.0 - Go 1.13)文章,对于初学者而言,知道语言的变化也是很重要的(比如方便的知道哪些问题解决了,哪些还没有被解决),可能之后会拓展性的写一篇关于这个的文章。
查看原文

赞 18 收藏 7 评论 3

陈星星 发布了文章 · 4月7日

数据结构和算法(Golang实现)(1)简单入门Golang-前言

数据结构和算法在计算机科学里,有非常重要的地位。此系列文章尝试使用 Golang 编程语言来实现各种数据结构和算法,并且适当进行算法分析。

我们会先简单学习一下Golang,然后进入计算机程序世界的第一个大门。

简单入门Golang

我们只学Golang语言的一个子集,足以开展接下来数据结构和算法的实现即可。

一、前言

Golang语言是谷歌Google公司在2007年启动,并在2009年正式发布并开源的高级编程语言。开源地址:https://github.com/golang/go,官网地址:https://golang.org

Golang语言语法简单,支持多平台交叉编译(Linux/Mac/Windows),支持内存自动GC(垃圾回收),支持嵌C/C++开发,并且实现了语法层面的线程调度,开发多线程程序十分方便。语法很像C/Python/JavaScript等高级编程语言。

设计这门语言的设计者有以下几位:

  1. Ken Thompson:在贝尔实验室与Dennis M. Ritche发明了C语言和Unix操作系统,与Rob Pike发明了UTF-8编码,图灵奖得主。
  2. Rob Pike:也参与开发了Unix操作系统,UTF-8编码发明者之一。
  3. Robert Griesemer:参与过V8 JavaScript引擎和Java HotSpot虚拟机的研发。

前两位比较知名,现在都已经退休了,其他人有兴趣可以谷歌一下。

二、安装并简单使用

安装Golang:https://golang.org/dl:Windows 操作系统点击msi按提示安装,Mac 操作系统可以使用brew install golang安装。

打开命令行终端输入:

go version

显示以下结果即为成功:

go version go1.13 darwin/amd64

在任一文件夹下新建一个文件main.goGolang语言编写的程序文件后缀必须都为.go):

package main

import (
    "fmt"
    "time"
)

func init() {
    fmt.Println("init will be before hello world")
}

func main() {
    fmt.Println("hello world")
    fmt.Println("today times:" + time.Now().String())
}

打开命令行终端进行编译:

go build main.go

编译后会在本地文件夹下生成一个二进制文件:main或者main.exe(Windows系统)。

执行二进制:

./main

将会打印出以下结果:

init will be before hello world
hello world
today times:2019-12-09 13:14:14.383118 +0800 CST m=+0.000199077

三、如何学习一门语言

每学一门编程语言,都离不开学习它的语言特征:

  1. 支持哪些基本数据类型,如整数,浮点数,布尔值,字符串,支持哪些高级数据类型,如数组,结构体等。
  2. if判断和while循环语句是怎样的,是否有switch或者goto等语句。
  3. 语言函数的定义是怎样的,如何传递函数参数,有没有面向对象的语言特征等。
  4. package包管理是怎样的,如何管理一个工程,官方提供哪些标准库,如时间处理,字符串处理,HTTP 库,加密库等。
  5. 有没有特殊的语言特征,其他语言没有的,比如某些语法糖。

系列文章入口

我是陈星星,欢迎阅读我亲自写的 数据结构和算法(Golang实现),文章首发于 阅读更友好的GitBook

查看原文

赞 18 收藏 12 评论 1

陈星星 回答了问题 · 2019-10-29

golang 中 websocket 读取消息的问题!

你可以写一个结构体,然后使用json反序列化:

{"proto":100,"data":{"id":1,"msg":"消息"}}
{"proto":101,"data":{"id":1,"msg":"消息"}}

package main

import (
    "encoding/json"
    "fmt"
)

type Message struct {
    Proto int64         `json:"proto"`
    Data  DataInMessage `json:"data"`
}

type DataInMessage struct {
    Id  int64  `json:"id"`
    Msg string `json:"msg"`
}

func ParseMessage(raw []byte) (message *Message, err error) {
    message = new(Message)
    err = json.Unmarshal(raw, message)
    return
}

func main() {
    raw := []byte(`{"proto":100,"data":{"id":1,"msg":"消息"}}`)
    m, err := ParseMessage(raw)
    if err != nil {
        fmt.Println(err.Error())
    } else {
        fmt.Println(m.Proto, m.Data)
    }
}

关注 6 回答 6

陈星星 提出了问题 · 2019-10-28

类似百度网盘:虚拟文件夹移动、复制如何实现

小弟我现在遇到了一个业务问题:

我们要实现一个类似百度网盘的产品,文件全存在对象存储里面,所以不需要考虑存储问题,只需要考虑这些虚拟文件信息的映射关系。目前只能用MySQL数据库。

原型的需求是:

  1. 文件夹支持复制到其他文件夹下,类比我们Windows上的文件夹复制。
  2. 文件夹支持移动到其他文件夹下,类比我们Windows上的文件夹移动。

大家都知道,比如有一个文件夹A,然后A下面有无数的子文件夹,你复制文件夹的时候,你要把该A文件夹下面的所有文件记录都查出来,然后插一份新的记录。

数据库要怎么实现,这种有上下级关联的,C的父文件夹是B,B的父文件夹是A,时间复杂度很高。递归select MySQL是不可能的。

关注 2 回答 1

陈星星 回答了问题 · 2019-08-20

nuxt.js搭建项目框架时,layout布局中的default默认文件

默认就是

<template>
    <div>
        <nuxt/>
    </div>
</template>

你可以不用改他。必须只能有一个根标记。然后<nuxt/>放在里面,这个<nuxt/>就是你 page页面具体*.vue下的模板内容。

关注 2 回答 1

陈星星 回答了问题 · 2019-08-20

goland报错 can't load package: package main

因为在同一个文件夹下,你的*.go文件,可能存在 package *,声明了不同的包。同一目录下,包名必须唯一。

关注 4 回答 3

认证与成就

  • 获得 84 次点赞
  • 获得 17 枚徽章 获得 0 枚金徽章, 获得 3 枚银徽章, 获得 14 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-08-16
个人主页被 1.1k 人浏览