标签 Go 下的文章

使用 Go 版本管理器管理多个版本的 Go 语言环境及其模块。

Go 语言版本管理器(GVM)是管理 Go 语言环境的开源工具。GVM “pkgsets” 支持安装多个版本的 Go 并管理每个项目的模块。它最初由 Josh Bussdieker 开发,GVM(像它的对手 Ruby RVM 一样)允许你为每个项目或一组项目创建一个开发环境,分离不同的 Go 版本和包依赖关系,以提供更大的灵活性,防止不同版本造成的问题。

有几种管理 Go 包的方式,包括内置于 Go 中的 Go 1.11 的 Modules。我发现 GVM 简单直观,即使我不用它来管理包,我还是会用它来管理 Go 不同的版本的。

安装 GVM

安装 GVM 很简单。GVM 存储库安装文档指示你下载安装程序脚本并将其传送到 Bash 来安装:

bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer)

尽管越来越多的人采用这种安装方法,但是在安装之前先看看安装程序在做什么仍然是一个很好的想法。以 GVM 为例,该安装程序脚本:

  1. 检查一些相关依赖性
  2. 克隆 GVM 存储库
  3. 使用 shell 脚本:

    • 安装 Go 语言
    • 管理 GOPATH 环境变量
    • bashrczshrc 或配置文件中添加一行内容

如果你想确认它在做什么,你可以克隆该存储库并查看 shell 脚本,然后运行 ./binscripts/gvm-installer 这个本地脚本进行设置。

注意: 因为 GVM 可以用来下载和编译新的 Go 版本,所以有一些预期的依赖关系,如 Make、Git 和 Curl。你可以在 GVM 的自述文件中找到完整的发行版列表。

使用 GVM 安装和管理 GO 版本

一旦安装了 GVM,你就可以使用它来安装和管理不同版本的 Go。gvm listall 命令显示可下载和编译的可用版本的 Go:

[chris@marvin ]$ gvm listall

gvm gos (available)

   go1
   go1.0.1
   go1.0.2
   go1.0.3

<输出截断>

安装特定的 Go 版本就像 gvm install <版本> 一样简单,其中 <版本>gvm listall 命令返回的版本之一。

假设你正在进行一个使用 Go1.12.8 版本的项目。你可以使用 gvm install go1.12.8 安装这个版本:

[chris@marvin]$ gvm install go1.12.8
Installing go1.12.8...
 * Compiling...
go1.12.8 successfully installed!

输入 gvm list,你会看到 Go 版本 1.12.8 与系统 Go 版本(使用操作系统的软件包管理器打包的版本)一起并存:

[chris@marvin]$ gvm list

gvm gos (installed)

   go1.12.8
=> system

GVM 仍在使用系统版本的 Go ,由 => 符号表示。你可以使用 gvm use 命令切换你的环境以使用新安装的 go1.12.8:

[chris@marvin]$ gvm use go1.12.8
Now using version go1.12.8

[chris@marvin]$ go version
go version go1.12.8 linux/amd64

GVM 使管理已安装版本的 Go 变得极其简单,但它不止于此!

使用 GVM pkgset

开箱即用,Go 有一种出色而令人沮丧的管理包和模块的方式。默认情况下,如果你 go get 获取一个包,它将被下载到 $GOPATH 目录中的 srcpkg 目录下,然后可以使用 import 将其包含在你的 Go 程序中。这使得获得软件包变得很容易,特别是对于非特权用户,而不需要 sudo 或 root 特权(很像 Python 中的 pip install --user)。然而,在不同的项目中管理相同包的不同版本是非常困难的。

有许多方法可以尝试修复或缓解这个问题,包括实验性 Go Modules(Go 1.11 版中增加了初步支持)和 Go dep(Go Modules 的“官方实验”并且持续迭代)。在我发现 GVM 之前,我会在一个 Go 项目自己的 Docker 容器中构建和测试它,以确保分离。

GVM 通过使用 “pkgsets” 将项目的新目录附加到安装的 Go 版本的默认 $GOPATH 上,很好地实现了项目之间包的管理和隔离,就像 $PATH 在 Unix/Linux 系统上工作一样。

想象它如何运行的。首先,安装新版 Go 1.12.9:

[chris@marvin]$ echo $GOPATH
/home/chris/.gvm/pkgsets/go1.12.8/global

[chris@marvin]$ gvm install go1.12.9
Installing go1.12.9...
 * Compiling...
go1.12.9 successfully installed

[chris@marvin]$ gvm use go1.12.9
Now using version go1.12.9

当 GVM 被告知使用新版本时,它会更改为新的 $GOPATH,默认 gloabl pkgset 应用于该版本:

[chris@marvin]$ echo $GOPATH
/home/chris/.gvm/pkgsets/go1.12.9/global

[chris@marvin]$ gvm pkgset list

gvm go package sets (go1.12.9)

=>  global

尽管默认情况下没有安装额外的包,但是全局 pkgset 中的包对于使用该特定版本的 Go 的任何项目都是可用的。

现在,假设你正在启用一个新项目,它需要一个特定的包。首先,使用 GVM 创建一个新的 pkgset,名为 introToGvm:

[chris@marvin]$ gvm pkgset create introToGvm

[chris@marvin]$ gvm pkgset use introToGvm
Now using version go1.12.9@introToGvm

[chris@marvin]$ gvm pkgset list

gvm go package sets (go1.12.9)

    global
=>  introToGvm

如上所述,pkgset 的一个新目录被添加到 $GOPATH

[chris@marvin]$ echo $GOPATH
/home/chris/.gvm/pkgsets/go1.12.9/introToGvm:/home/chris/.gvm/pkgsets/go1.12.9/global

将目录更改为预先设置的 introToGvm 路径,检查目录结构,这里使用 awkbash 完成。

[chris@marvin]$ cd $( awk -F':' '{print $1}' <<< $GOPATH )
[chris@marvin]$ pwd
/home/chris/.gvm/pkgsets/go1.12.9/introToGvm

[chris@marvin]$ ls
overlay  pkg  src

请注意,新目录看起来很像普通的 $GOPATH。新的 Go 包使用同样的 go get 命令下载并正常使用,且添加到 pkgset 中。

例如,使用以下命令获取 gorilla/mux 包,然后检查 pkgset 的目录结构:

[chris@marvin]$ go get github.com/gorilla/mux
[chris@marvin]$ tree
[chris@marvin introToGvm ]$ tree
.
├── overlay
│   ├── bin
│   └── lib
│       └── pkgconfig
├── pkg
│   └── linux_amd64
│       └── github.com
│           └── gorilla
│               └── mux.a
src/
└── github.com
    └── gorilla
        └── mux
            ├── AUTHORS
            ├── bench_test.go
            ├── context.go
            ├── context_test.go
            ├── doc.go
            ├── example_authentication_middleware_test.go
            ├── example_cors_method_middleware_test.go
            ├── example_route_test.go
            ├── go.mod
            ├── LICENSE
            ├── middleware.go
            ├── middleware_test.go
            ├── mux.go
            ├── mux_test.go
            ├── old_test.go
            ├── README.md
            ├── regexp.go
            ├── route.go
            └── test_helpers.go

如你所见,gorilla/mux 已按预期添加到 pkgset $GOPATH 目录中,现在可用于使用此 pkgset 项目了。

GVM 让 Go 管理变得轻而易举

GVM 是一种直观且非侵入性的管理 Go 版本和包的方式。它可以单独使用,也可以与其他 Go 模块管理技术结合使用并利用 GVM Go 版本管理功能。按 Go 版本和包依赖来分离项目使得开发更加容易,并且减少了管理版本冲突的复杂性,GVM 让这变得轻而易举。


via: https://opensource.com/article/19/10/introduction-gvm

作者:Chris Collins 选题:lujun9972 译者:heguangzhi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Go 语言,能在多低下的配置上运行并发挥作用呢?

我最近购买了一个特别便宜的开发板:

STM32F030F4P6

我购买它的理由有三个。首先,我(作为程序员)从未接触过 STM320 系列的开发板。其次,STM32F10x 系列使用也有点少了。STM320 系列的 MCU 很便宜,有更新一些的外设,对系列产品进行了改进,问题修复也做得更好了。最后,为了这篇文章,我选用了这一系列中最低配置的开发板,整件事情就变得有趣起来了。

硬件部分

STM32F030F4P6 给人留下了很深的印象:

  • CPU: Cortex M0 48 MHz(最低配置,只有 12000 个逻辑门电路)
  • RAM: 4 KB,
  • Flash: 16 KB,
  • ADC、SPI、I2C、USART 和几个定时器

以上这些采用了 TSSOP20 封装。正如你所见,这是一个很小的 32 位系统。

软件部分

如果你想知道如何在这块开发板上使用 Go 编程,你需要反复阅读硬件规范手册。你必须面对这样的真实情况:在 Go 编译器中给 Cortex-M0 提供支持的可能性很小。而且,这还仅仅只是第一个要解决的问题。

我会使用 Emgo,但别担心,之后你会看到,它如何让 Go 在如此小的系统上尽可能发挥作用。

在我拿到这块开发板之前,对 stm32/hal 系列下的 F0 MCU 没有任何支持。在简单研究参考手册后,我发现 STM32F0 系列是 STM32F3 削减版,这让在新端口上开发的工作变得容易了一些。

如果你想接着本文的步骤做下去,需要先安装 Emgo

cd $HOME
git clone https://github.com/ziutek/emgo/
cd emgo/egc
go install

然后设置一下环境变量

export EGCC=path_to_arm_gcc      # eg. /usr/local/arm/bin/arm-none-eabi-gcc
export EGLD=path_to_arm_linker   # eg. /usr/local/arm/bin/arm-none-eabi-ld
export EGAR=path_to_arm_archiver # eg. /usr/local/arm/bin/arm-none-eabi-ar

export EGROOT=$HOME/emgo/egroot
export EGPATH=$HOME/emgo/egpath

export EGARCH=cortexm0
export EGOS=noos
export EGTARGET=f030x6

更详细的说明可以在 Emgo 官网上找到。

要确保 egc 在你的 PATH 中。 你可以使用 go build 来代替 go install,然后把 egc 复制到你的 $HOME/bin/usr/local/bin 中。

现在,为你的第一个 Emgo 程序创建一个新文件夹,随后把示例中链接器脚本复制过来:

mkdir $HOME/firstemgo
cd $HOME/firstemgo
cp $EGPATH/src/stm32/examples/f030-demo-board/blinky/script.ld .

最基本程序

main.go 文件中创建一个最基本的程序:

package main

func main() {
}

文件编译没有出现任何问题:

$ egc
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
   7452     172     104    7728    1e30 cortexm0.elf

第一次编译可能会花点时间。编译后产生的二进制占用了 7624 个字节的 Flash 空间(文本 + 数据)。对于一个什么都没做的程序来说,占用的空间有些大。还剩下 8760 字节,可以用来做些有用的事。

不妨试试传统的 “Hello, World!” 程序:

package main

import "fmt"

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

不幸的是,这次结果有些糟糕:

$ egc
/usr/local/arm/bin/arm-none-eabi-ld: /home/michal/P/go/src/github.com/ziutek/emgo/egpath/src/stm32/examples/f030-demo-board/blog/cortexm0.elf section `.text' will not fit in region `Flash'
/usr/local/arm/bin/arm-none-eabi-ld: region `Flash' overflowed by 10880 bytes
exit status 1

“Hello, World!” 需要 STM32F030x6 上至少 32KB 的 Flash 空间。

fmt 包强制包含整个 strconvreflect 包。这三个包,即使在精简版本中的 Emgo 中,占用空间也很大。我们不能使用这个例子了。有很多的应用不需要好看的文本输出。通常,一个或多个 LED,或者七段数码管显示就足够了。不过,在第二部分,我会尝试使用 strconv 包来格式化,并在 UART 上显示一些数字和文本。

闪烁

我们的开发板上有一个与 PA4 引脚和 VCC 相连的 LED。这次我们的代码稍稍长了一些:

package main

import (
    "delay"

    "stm32/hal/gpio"
    "stm32/hal/system"
    "stm32/hal/system/timer/systick"
)

var led gpio.Pin

func init() {
    system.SetupPLL(8, 1, 48/8)
    systick.Setup(2e6)

    gpio.A.EnableClock(false)
    led = gpio.A.Pin(4)

    cfg := &gpio.Config{Mode: gpio.Out, Driver: gpio.OpenDrain}
    led.Setup(cfg)
}

func main() {
    for {
        led.Clear()
        delay.Millisec(100)
        led.Set()
        delay.Millisec(900)
    }
}

按照惯例,init 函数用来初始化和配置外设。

system.SetupPLL(8, 1, 48/8) 用来配置 RCC,将外部的 8 MHz 振荡器的 PLL 作为系统时钟源。PLL 分频器设置为 1,倍频数设置为 48/8 =6,这样系统时钟频率为 48MHz。

systick.Setup(2e6) 将 Cortex-M SYSTICK 时钟作为系统时钟,每隔 2e6 次纳秒运行一次(每秒钟 500 次)。

gpio.A.EnableClock(false) 开启了 GPIO A 口的时钟。False 意味着这一时钟在低功耗模式下会被禁用,但在 STM32F0 系列中并未实现这一功能。

led.Setup(cfg) 设置 PA4 引脚为开漏输出。

led.Clear() 将 PA4 引脚设为低,在开漏设置中,打开 LED。

led.Set() 将 PA4 设为高电平状态,关掉LED。

编译这个代码:

$ egc
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
   9772     172     168   10112    2780 cortexm0.elf

正如你所看到的,这个闪烁程序占用了 2320 字节,比最基本程序占用空间要大。还有 6440 字节的剩余空间。

看看代码是否能运行:

$ openocd -d0 -f interface/stlink.cfg -f target/stm32f0x.cfg -c 'init; program cortexm0.elf; reset run; exit'
Open On-Chip Debugger 0.10.0+dev-00319-g8f1f912a (2018-03-07-19:20)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
debug_level: 0
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
none separate
adapter speed: 950 kHz
target halted due to debug-request, current mode: Thread 
xPSR: 0xc1000000 pc: 0x0800119c msp: 0x20000da0
adapter speed: 4000 kHz
** Programming Started **
auto erase enabled
target halted due to breakpoint, current mode: Thread 
xPSR: 0x61000000 pc: 0x2000003a msp: 0x20000da0
wrote 10240 bytes from file cortexm0.elf in 0.817425s (12.234 KiB/s)
** Programming Finished **
adapter speed: 950 kHz

在这篇文章中,这是我第一次,将一个短视频转换成动画 PNG。我对此印象很深,再见了 YouTube。 对于 IE 用户,我很抱歉,更多信息请看 apngasm。我本应该学习 HTML5,但现在,APNG 是我最喜欢的,用来播放循环短视频的方法了。

STM32F030F4P6

更多的 Go 语言编程

如果你不是一个 Go 程序员,但你已经听说过一些关于 Go 语言的事情,你可能会说:“Go 语法很好,但跟 C 比起来,并没有明显的提升。让我看看 Go 语言的通道和协程!”

接下来我会一一展示:

import (
    "delay"

    "stm32/hal/gpio"
    "stm32/hal/system"
    "stm32/hal/system/timer/systick"
)

var led1, led2 gpio.Pin

func init() {
    system.SetupPLL(8, 1, 48/8)
    systick.Setup(2e6)

    gpio.A.EnableClock(false)
    led1 = gpio.A.Pin(4)
    led2 = gpio.A.Pin(5)

    cfg := &gpio.Config{Mode: gpio.Out, Driver: gpio.OpenDrain}
    led1.Setup(cfg)
    led2.Setup(cfg)
}

func blinky(led gpio.Pin, period int) {
    for {
        led.Clear()
        delay.Millisec(100)
        led.Set()
        delay.Millisec(period - 100)
    }
}

func main() {
    go blinky(led1, 500)
    blinky(led2, 1000)
}

代码改动很小: 添加了第二个 LED,上一个例子中的 main 函数被重命名为 blinky 并且需要提供两个参数。 main 在新的协程中先调用 blinky,所以两个 LED 灯在并行使用。值得一提的是,gpio.Pin 可以同时访问同一 GPIO 口的不同引脚。

Emgo 还有很多不足。其中之一就是你需要提前规定 goroutines(tasks) 的最大执行数量。是时候修改 script.ld 了:

ISRStack = 1024;
MainStack = 1024;
TaskStack = 1024;
MaxTasks = 2;

INCLUDE stm32/f030x4
INCLUDE stm32/loadflash
INCLUDE noos-cortexm

栈的大小需要靠猜,现在还不用关心这一点。

$ egc
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  10020     172     172   10364    287c cortexm0.elf

另一个 LED 和协程一共占用了 248 字节的 Flash 空间。

STM32F030F4P6

通道

通道是 Go 语言中协程之间相互通信的一种推荐方式。Emgo 甚至能允许通过中断处理来使用缓冲通道。下一个例子就展示了这种情况。

package main

import (
    "delay"
    "rtos"

    "stm32/hal/gpio"
    "stm32/hal/irq"
    "stm32/hal/system"
    "stm32/hal/system/timer/systick"
    "stm32/hal/tim"
)

var (
    leds  [3]gpio.Pin
    timer *tim.Periph
    ch    = make(chan int, 1)
)

func init() {
    system.SetupPLL(8, 1, 48/8)
    systick.Setup(2e6)

    gpio.A.EnableClock(false)
    leds[0] = gpio.A.Pin(4)
    leds[1] = gpio.A.Pin(5)
    leds[2] = gpio.A.Pin(9)

    cfg := &gpio.Config{Mode: gpio.Out, Driver: gpio.OpenDrain}
    for _, led := range leds {
        led.Set()
        led.Setup(cfg)
    }

    timer = tim.TIM3
    pclk := timer.Bus().Clock()
    if pclk < system.AHB.Clock() {
        pclk *= 2
    }
    freq := uint(1e3) // Hz
    timer.EnableClock(true)
    timer.PSC.Store(tim.PSC(pclk/freq - 1))
    timer.ARR.Store(700) // ms
    timer.DIER.Store(tim.UIE)
    timer.CR1.Store(tim.CEN)

    rtos.IRQ(irq.TIM3).Enable()
}

func blinky(led gpio.Pin, period int) {
    for range ch {
        led.Clear()
        delay.Millisec(100)
        led.Set()
        delay.Millisec(period - 100)
    }
}

func main() {
    go blinky(leds[1], 500)
    blinky(leds[2], 500)
}

func timerISR() {
    timer.SR.Store(0)
    leds[0].Set()
    select {
    case ch <- 0:
        // Success
    default:
        leds[0].Clear()
    }
}

//c:__attribute__((section(".ISRs")))
var ISRs = [...]func(){
    irq.TIM3: timerISR,
}

与之前例子相比较下的不同:

  1. 添加了第三个 LED,并连接到 PA9 引脚(UART 头的 TXD 引脚)。
  2. 时钟(TIM3)作为中断源。
  3. 新函数 timerISR 用来处理 irq.TIM3 的中断。
  4. 新增容量为 1 的缓冲通道是为了 timerISRblinky 协程之间的通信。
  5. ISRs 数组作为中断向量表,是更大的异常向量表的一部分。
  6. blinky 中的 for 语句被替换成 range 语句。

为了方便起见,所有的 LED,或者说它们的引脚,都被放在 leds 这个数组里。另外,所有引脚在被配置为输出之前,都设置为一种已知的初始状态(高电平状态)。

在这个例子里,我们想让时钟以 1 kHz 的频率运行。为了配置 TIM3 预分频器,我们需要知道它的输入时钟频率。通过参考手册我们知道,输入时钟频率在 APBCLK = AHBCLK 时,与 APBCLK 相同,反之等于 2 倍的 APBCLK

如果 CNT 寄存器增加 1 kHz,那么 ARR 寄存器的值等于更新事件(重载事件)在毫秒中的计数周期。 为了让更新事件产生中断,必须要设置 DIER 寄存器中的 UIE 位。CEN 位能启动时钟。

时钟外设在低功耗模式下必须启用,为了自身能在 CPU 处于休眠时保持运行: timer.EnableClock(true)。这在 STM32F0 中无关紧要,但对代码可移植性却十分重要。

timerISR 函数处理 irq.TIM3 的中断请求。timer.SR.Store(0) 会清除 SR 寄存器里的所有事件标志,无效化向 NVIC 发出的所有中断请求。凭借经验,由于中断请求无效的延时性,需要在程序一开始马上清除所有的中断标志。这避免了无意间再次调用处理。为了确保万无一失,需要先清除标志,再读取,但是在我们的例子中,清除标志就已经足够了。

下面的这几行代码:

select {
case ch <- 0:
    // Success
default:
    leds[0].Clear()
}

是 Go 语言中,如何在通道上非阻塞地发送消息的方法。中断处理程序无法一直等待通道中的空余空间。如果通道已满,则执行 default,开发板上的LED就会开启,直到下一次中断。

ISRs 数组包含了中断向量表。//c:__attribute__((section(".ISRs"))) 会导致链接器将数组插入到 .ISRs 节中。

blinkyfor 循环的新写法:

for range ch {
    led.Clear()
    delay.Millisec(100)
    led.Set()
    delay.Millisec(period - 100)
}

等价于:

for {
    _, ok := <-ch
    if !ok {
        break // Channel closed.
    }
    led.Clear()
    delay.Millisec(100)
    led.Set()
    delay.Millisec(period - 100)
}

注意,在这个例子中,我们不在意通道中收到的值,我们只对其接受到的消息感兴趣。我们可以在声明时,将通道元素类型中的 int 用空结构体 struct{} 来代替,发送消息时,用 struct{}{} 结构体的值代替 0,但这部分对新手来说可能会有些陌生。

让我们来编译一下代码:

$ egc
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  11096     228     188   11512    2cf8 cortexm0.elf

新的例子占用了 11324 字节的 Flash 空间,比上一个例子多占用了 1132 字节。

采用现在的时序,两个闪烁协程从通道中获取数据的速度,比 timerISR 发送数据的速度要快。所以它们在同时等待新数据,你还能观察到 select 的随机性,这也是 Go 规范所要求的。

STM32F030F4P6

开发板上的 LED 一直没有亮起,说明通道从未出现过溢出。

我们可以加快消息发送的速度,将 timer.ARR.Store(700) 改为 timer.ARR.Store(200)。 现在 timerISR 每秒钟发送 5 条消息,但是两个接收者加起来,每秒也只能接受 4 条消息。

STM32F030F4P6

正如你所看到的,timerISR 开启黄色 LED 灯,意味着通道上已经没有剩余空间了。

第一部分到这里就结束了。你应该知道,这一部分并未展示 Go 中最重要的部分,接口。

协程和通道只是一些方便好用的语法。你可以用自己的代码来替换它们,这并不容易,但也可以实现。接口是Go 语言的基础。这是文章中 第二部分所要提到的.

在 Flash 上我们还有些剩余空间。


via: https://ziutek.github.io/2018/03/30/go_on_very_small_hardware.html

作者:Michał Derkacz 译者:wenwensnow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Go 编程语言于 2009 年首次公开发布,此后被广泛使用。特别是,Go 已经成为云基础设施领域的一种代表性语言,例如 KubernetesOpenShiftTerraform 等大型项目都使用了 Go。

Go 越来越受欢迎的原因是性能好、易于编写高并发的程序、语法简单和编译快。

让我们来看看如何在 Fedora 上开始 Go 语言编程吧。

在 Fedora 上安装 Go

Fedora 可以通过官方库简单快速地安装 Go 语言。

$ sudo dnf install -y golang
$ go version
go version go1.12.7 linux/amd64

既然装好了 Go ,让我们来写个简单的程序,编译并运行。

第一个 Go 程序

让我们来用 Go 语言写一波 “Hello, World!”。首先创建 main.go 文件,然后输入或者拷贝以下内容。

package main

import "fmt"

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

运行这个程序很简单。

$ go run main.go
Hello, World!

Go 会在临时目录将 main.go 编译成二进制文件并执行,然后删除临时目录。这个命令非常适合在开发过程中快速运行程序,它还凸显了 Go 的编译速度。

编译一个可执行程序就像运行它一样简单。

$ go build main.go
$ ./main
Hello, World!

使用 Go 的模块

Go 1.11 和 1.12 引入了对模块的初步支持。模块可用于管理应用程序的各种依赖包。Go 通过 go.modgo.sum 这两个文件,显式地定义依赖包的版本。

为了演示如何使用模块,让我们为 hello world 程序添加一个依赖。

在更改代码之前,需要初始化模块。

$ go mod init helloworld
go: creating new go.mod: module helloworld
$ ls
go.mod main  main.go

然后按照以下内容修改 main.go 文件。

package main

import "github.com/fatih/color"

func main () {
     color.Blue("Hello, World!")
}

在修改后的 main.go 中,不再使用标准库 fmt 来打印 “Hello, World!” ,而是使用第三方库打印出有色字体。

让我们来跑一下新版的程序吧。

$ go run main.go
Hello, World!

因为程序依赖于 github.com/fatih/color 库,它需要在编译前下载所有依赖包。 然后把依赖包都添加到 go.mod 中,并将它们的版本号和哈希值记录在 go.sum 中。


via: https://fedoramagazine.org/getting-started-with-go-on-fedora/

作者:Clément Verna 选题:lujun9972 译者:hello-wn 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

(以下内容是我的硕士论文的摘录,几乎是整个 2.1 章节,向具有 CS 背景的人快速介绍 Go)

Go 是一门用于并发编程的命令式编程语言,它主要由创造者 Google 进行开发,最初主要由 Robert Griesemer、Rob Pike 和 Ken Thompson 开发。这门语言的设计起始于 2007 年,并在 2009 年推出最初版本;而第一个稳定版本是 2012 年发布的 1.0 版本。 1

Go 有 C 风格的语法(没有预处理器)、垃圾回收机制,而且类似它在贝尔实验室里被开发出来的前辈们:Newsqueak(Rob Pike)、Alef(Phil Winterbottom)和 Inferno(Pike、Ritchie 等人),使用所谓的 Go 协程 goroutines 信道 channels (一种基于 Hoare 的“通信顺序进程”理论的协程)提供内建的并发支持。 2

Go 程序以包的形式组织。包本质是一个包含 Go 文件的文件夹。包内的所有文件共享相同的命名空间,而包内的符号有两种可见性:以大写字母开头的符号对于其他包是可见,而其他符号则是该包私有的:

func PublicFunction() {
    fmt.Println("Hello world")
}

func privateFunction() {
    fmt.Println("Hello package")
}

类型

Go 有一个相当简单的类型系统:没有子类型(但有类型转换),没有泛型,没有多态函数,只有一些基本的类型:

  1. 基本类型:intint64int8uintfloat32float64
  2. struct
  3. interface:一组方法的集合
  4. map[K, V]:一个从键类型到值类型的映射
  5. [number]Type:一些 Type 类型的元素组成的数组
  6. []Type:某种类型的切片(具有长度和功能的数组的指针)
  7. chan Type:一个线程安全的队列
  8. 指针 *T 指向其他类型
  9. 函数
  10. 具名类型:可能具有关联方法的其他类型的别名(LCTT 译注:这里的别名并非指 Go 1.9 中的新特性“类型别名”):
  type T struct { foo int }
  type T *T
  type T OtherNamedType

具名类型完全不同于它们的底层类型,所以你不能让它们互相赋值,但一些操作符,例如 +,能够处理同一底层数值类型的具名类型对象们(所以你可以在上面的示例中把两个 T 加起来)。

映射、切片和信道是类似于引用的类型——它们实际上是包含指针的结构。包括数组(具有固定长度并可被拷贝)在内的其他类型则是值传递(拷贝)。

类型转换

类型转换类似于 C 或其他语言中的类型转换。它们写成这样子:

TypeName(value)

常量

Go 有“无类型”字面量和常量。

1 // 无类型整数字面量
const foo = 1 // 无类型整数常量
const foo int = 1 // int 类型常量

无类型值可以分为以下几类:UntypedBoolUntypedIntUntypedRuneUntypedFloatUntypedComplexUntypedString 以及 UntypedNil(Go 称它们为基础类型,其他基础种类可用于具体类型,如 uint8)。一个无类型值可以赋值给一个从基础类型中派生的具名类型;例如:

type someType int

const untyped = 2 // UntypedInt
const bar someType = untyped // OK: untyped 可以被赋值给 someType
const typed int = 2 // int
const bar2 someType = typed // error: int 不能被赋值给 someType

接口和对象

正如上面所说的,接口是一组方法的集合。Go 本身不是一种面向对象的语言,但它支持将方法关联到具名类型上:当声明一个函数时,可以提供一个接收者。接收者是函数的一个额外参数,可以在函数之前传递并参与函数查找,就像这样:

type SomeType struct { ... }
type SomeType struct { ... }

func (s *SomeType) MyMethod() {
}

func main() {
    var s SomeType
    s.MyMethod()
}

如果对象实现了所有方法,那么它就实现了接口;例如,*SomeType(注意指针)实现了下面的接口 MyMethoder,因此 *SomeType 类型的值就能作为 MyMethoder 类型的值使用。最基本的接口类型是 interface{},它是一个带空方法集的接口 —— 任何对象都满足该接口。

type MyMethoder interface {
    MyMethod()
}

合法的接收者类型是有些限制的;例如,具名类型可以是指针类型(例如,type MyIntPointer *int),但这种类型不是合法的接收者类型。

控制流

Go 提供了三个主要的控制了语句:ifswitchfor。这些语句同其他 C 风格语言内的语句非常类似,但有一些不同:

  • 条件语句没有括号,所以条件语句是 if a == b {} 而不是 if (a == b) {}。大括号是必须的。
  • 所有的语句都可以有初始化,比如这个 if result, err := someFunction(); err == nil { // use result }
  • switch 语句在分支里可以使用任何表达式
  • switch 语句可以处理空的表达式(等于 true
  • 默认情况下,Go 不会从一个分支进入下一个分支(不需要 break 语句),在程序块的末尾使用 fallthrough 则会进入下一个分支。
  • 循环语句 for 不仅能循环值域:for key, val := range map { do something }

Go 协程

关键词 go 会产生一个新的 Go 协程 goroutine ,这是一个可以并行执行的函数。它可以用于任何函数调用,甚至一个匿名函数:

func main() {
    ...
    go func() {
        ...
    }()

    go some_function(some_argument)
}

信道

Go 协程通常和信道channels结合,用来提供一种通信顺序进程的扩展。信道是一个并发安全的队列,而且可以选择是否缓冲数据:

var unbuffered = make(chan int) // 直到数据被读取时完成数据块发送
var buffered = make(chan int, 5) // 最多有 5 个未读取的数据块

运算符 <- 用于和单个信道进行通信。

valueReadFromChannel := <- channel
otherChannel <- valueToSend

语句 select 允许多个信道进行通信:

select {
    case incoming := <- inboundChannel:
    // 一条新消息
    case outgoingChannel <- outgoing:
    // 可以发送消息
}

defer 声明

Go 提供语句 defer 允许函数退出时调用执行预定的函数。它可以用于进行资源释放操作,例如:

func myFunc(someFile io.ReadCloser) {
    defer someFile.close()
    /* 文件相关操作 */
}

当然,它允许使用匿名函数作为被调函数,而且编写被调函数时可以像平常一样使用任何变量。

错误处理

Go 没有提供异常类或者结构化的错误处理。然而,它通过第二个及后续的返回值来返回错误从而处理错误:

func Read(p []byte) (n int, err error)

// 内建类型:
type error interface {
    Error() string
}

必须在代码中检查错误或者赋值给 _

n0, _ := Read(Buffer) // 忽略错误
n, err := Read(buffer)
if err != nil {
    return err
}

有两个函数可以快速跳出和恢复调用栈:panic()recover()。当 panic() 被调用时,调用栈开始弹出,同时每个 defer 函数都会正常运行。当一个 defer 函数调用 recover()时,调用栈停止弹出,同时返回函数 panic() 给出的值。如果我们让调用栈正常弹出而不是由于调用 panic() 函数,recover() 将只返回 nil。在下面的例子中,defer 函数将捕获 panic() 抛出的任何 error 类型的值并储存在错误返回值中。第三方库中有时会使用这个方法增强递归代码的可读性,如解析器,同时保持公有函数仍使用普通错误返回值。

func Function() (err error) {
    defer func() {
        s := recover()
        switch s := s.(type) {  // type switch
            case error:
                err = s         // s has type error now
            default:
                panic(s)
        }
    }
}

数组和切片

正如前边说的,数组是值类型,而切片是指向数组的指针。切片可以由现有的数组切片产生,也可以使用 make() 创建切片,这会创建一个匿名数组以保存元素。

slice1 := make([]int, 2, 5) // 分配 5 个元素,其中 2 个初始化为0
slice2 := array[:] // 整个数组的切片
slice3 := array[1:] // 除了首元素的切片

除了上述例子,还有更多可行的切片运算组合,但需要明了直观。

使用 append() 函数,切片可以作为一个变长数组使用。

slice = append(slice, value1, value2)
slice = append(slice, arrayOrSlice...)

切片也可以用于函数的变长参数。

映射

映射 maps 是简单的键值对储存容器,并支持索引和分配。但它们不是线程安全的。

someValue := someMap[someKey]
someValue, ok := someMap[someKey] // 如果键值不在 someMap 中,变量 ok 会赋值为 `false`
someMap[someKey] = someValue

via: https://blog.jak-linux.org/2018/12/24/introduction-to-go/

作者:Julian Andres Klode 选题:lujun9972 译者:LazyWolfLin 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出


  1. Frequently Asked Questions (FAQ) - The Go Programming Language https://golang.org/doc/faq#history [return]
  2. HOARE, Charles Antony Richard. Communicating sequential processes. Communications of the ACM, 1978, 21. Jg., Nr. 8, S. 666-677. [return]

Anthony Starks 使用他出色的 Deck 演示工具重构了我原来的基于 Google Slides 的幻灯片。你可以在他的博客上查看他重构后的幻灯片,
mindchunk.blogspot.com.au/2014/06/remixing-with-deck

我最近被邀请在 Gocon 发表演讲,这是一个每半年在日本东京举行的 Go 的精彩大会。Gocon 2014 是一个完全由社区驱动的为期一天的活动,由培训和一整个下午的围绕着生产环境中的 Go 这个主题的演讲组成.(LCTT 译注:本文发表于 2014 年)

以下是我的讲义。原文的结构能让我缓慢而清晰的演讲,因此我已经编辑了它使其更可读。

我要感谢 Bill Kennedy 和 Minux Ma,特别是 Josh Bleecher Snyder,感谢他们在我准备这次演讲中的帮助。


大家下午好。

我叫 David.

我很高兴今天能来到 Gocon。我想参加这个会议已经两年了,我很感谢主办方能提供给我向你们演讲的机会。

Gocon 2014

我想以一个问题开始我的演讲。

为什么选择 Go?

当大家讨论学习或在生产环境中使用 Go 的原因时,答案不一而足,但因为以下三个原因的最多。

Gocon 2014

这就是 TOP3 的原因。

第一,并发。

Go 的 并发原语 Concurrency Primitives 对于来自 Nodejs,Ruby 或 Python 等单线程脚本语言的程序员,或者来自 C++ 或 Java 等重量级线程模型的语言都很有吸引力。

易于部署。

我们今天从经验丰富的 Gophers 那里听说过,他们非常欣赏部署 Go 应用的简单性。

Gocon 2014

然后是性能。

我相信人们选择 Go 的一个重要原因是它 快。

Gocon 2014 (4)

在今天的演讲中,我想讨论五个有助于提高 Go 性能的特性。

我还将与大家分享 Go 如何实现这些特性的细节。

Gocon 2014 (5)

我要谈的第一个特性是 Go 对于值的高效处理和存储。

Gocon 2014 (6)

这是 Go 中一个值的例子。编译时,gocon 正好消耗四个字节的内存。

让我们将 Go 与其他一些语言进行比较

Gocon 2014 (7)

由于 Python 表示变量的方式的开销,使用 Python 存储相同的值会消耗六倍的内存。

Python 使用额外的内存来跟踪类型信息,进行 引用计数 Reference Counting 等。

让我们看另一个例子:

Gocon 2014 (8)

与 Go 类似,Java 消耗 4 个字节的内存来存储 int 型。

但是,要在像 ListMap 这样的集合中使用此值,编译器必须将其转换为 Integer 对象。

Gocon 2014 (9)

因此,Java 中的整数通常消耗 16 到 24 个字节的内存。

为什么这很重要? 内存便宜且充足,为什么这个开销很重要?

Gocon 2014 (10)

这是一张显示 CPU 时钟速度与内存总线速度的图表。

请注意 CPU 时钟速度和内存总线速度之间的差距如何继续扩大。

两者之间的差异实际上是 CPU 花费多少时间等待内存。

Gocon 2014 (11)

自 1960 年代后期以来,CPU 设计师已经意识到了这个问题。

他们的解决方案是一个缓存,一个更小、更快的内存区域,介入 CPU 和主存之间。

Gocon 2014 (12)

这是一个 Location 类型,它保存物体在三维空间中的位置。它是用 Go 编写的,因此每个 Location 只消耗 24 个字节的存储空间。

我们可以使用这种类型来构造一个容纳 1000 个 Location 的数组类型,它只消耗 24000 字节的内存。

在数组内部,Location 结构体是顺序存储的,而不是随机存储的 1000 个 Location 结构体的指针。

这很重要,因为现在所有 1000 个 Location 结构体都按顺序放在缓存中,紧密排列在一起。

Gocon 2014 (13)

Go 允许您创建紧凑的数据结构,避免不必要的填充字节。

紧凑的数据结构能更好地利用缓存。

更好的缓存利用率可带来更好的性能。

Gocon 2014 (14)

函数调用不是无开销的。

Gocon 2014 (15)

调用函数时会发生三件事。

创建一个新的 栈帧 Stack Frame ,并记录调用者的详细信息。

在函数调用期间可能被覆盖的任何寄存器都将保存到栈中。

处理器计算函数的地址并执行到该新地址的分支。

Gocon 2014 (16)

由于函数调用是非常常见的操作,因此 CPU 设计师一直在努力优化此过程,但他们无法消除开销。

函调固有开销,或重于泰山,或轻于鸿毛,这取决于函数做了什么。

减少函数调用开销的解决方案是 内联 Inlining

Gocon 2014 (17)

Go 编译器通过将函数体视为调用者的一部分来内联函数。

内联也有成本,它增加了二进制文件大小。

只有当调用开销与函数所做工作关联度的很大时内联才有意义,因此只有简单的函数才能用于内联。

复杂的函数通常不受调用它们的开销所支配,因此不会内联。

Gocon 2014 (18)

这个例子显示函数 Double 调用 util.Max

为了减少调用 util.Max 的开销,编译器可以将 util.Max 内联到 Double 中,就象这样

Gocon 2014 (19)

内联后不再调用 util.Max,但是 Double 的行为没有改变。

内联并不是 Go 独有的。几乎每种编译或及时编译的语言都执行此优化。但是 Go 的内联是如何实现的?

Go 实现非常简单。编译包时,会标记任何适合内联的小函数,然后照常编译。

然后函数的源代码和编译后版本都会被存储。

Gocon 2014 (20)

此幻灯片显示了 util.a 的内容。源代码已经过一些转换,以便编译器更容易快速处理。

当编译器编译 Double 时,它看到 util.Max 可内联的,并且 util.Max 的源代码是可用的。

就会替换原函数中的代码,而不是插入对 util.Max 的编译版本的调用。

拥有该函数的源代码可以实现其他优化。

Gocon 2014 (21)

在这个例子中,尽管函数 Test 总是返回 false,但 Expensive 在不执行它的情况下无法知道结果。

Test 被内联时,我们得到这样的东西。

Gocon 2014 (22)

编译器现在知道 Expensive 的代码无法访问。

这不仅节省了调用 Test 的成本,还节省了编译或运行任何现在无法访问的 Expensive 代码。

Go 编译器可以跨文件甚至跨包自动内联函数。还包括从标准库调用的可内联函数的代码。

Gocon 2014 (23)

强制垃圾回收 Mandatory Garbage Collection 使 Go 成为一种更简单,更安全的语言。

这并不意味着垃圾回收会使 Go 变慢,或者垃圾回收是程序速度的瓶颈。

这意味着在堆上分配的内存是有代价的。每次 GC 运行时都会花费 CPU 时间,直到释放内存为止。

Gocon 2014 (24)

然而,有另一个地方分配内存,那就是栈。

与 C 不同,它强制您选择是否将值通过 malloc 将其存储在堆上,还是通过在函数范围内声明将其储存在栈上;Go 实现了一个名为 逃逸分析 Escape Analysis 的优化。

Gocon 2014 (25)

逃逸分析决定了对一个值的任何引用是否会从被声明的函数中逃逸。

如果没有引用逃逸,则该值可以安全地存储在栈中。

存储在栈中的值不需要分配或释放。

让我们看一些例子

Gocon 2014 (26)

Sum 返回 1 到 100 的整数的和。这是一种相当不寻常的做法,但它说明了逃逸分析的工作原理。

因为切片 numbers 仅在 Sum 内引用,所以编译器将安排到栈上来存储的 100 个整数,而不是安排到堆上。

没有必要回收 numbers,它会在 Sum 返回时自动释放。

Gocon 2014 (27)

第二个例子也有点尬。在 CenterCursor 中,我们创建一个新的 Cursor 对象并在 c 中存储指向它的指针。

然后我们将 c 传递给 Center() 函数,它将 Cursor 移动到屏幕的中心。

最后我们打印出那个 ‘Cursor` 的 X 和 Y 坐标。

即使 cnew 函数分配了空间,它也不会存储在堆上,因为没有引用 c 的变量逃逸 CenterCursor 函数。

Gocon 2014 (28)

默认情况下,Go 的优化始终处于启用状态。可以使用 -gcflags = -m 开关查看编译器的逃逸分析和内联决策。

因为逃逸分析是在编译时执行的,而不是运行时,所以无论垃圾回收的效率如何,栈分配总是比堆分配快。

我将在本演讲的其余部分详细讨论栈。

Gocon 2014 (30)

Go 有 goroutine。 这是 Go 并发的基石。

我想退一步,探索 goroutine 的历史。

最初,计算机一次运行一个进程。在 60 年代,多进程或 分时 Time Sharing 的想法变得流行起来。

在分时系统中,操作系统必须通过保护当前进程的现场,然后恢复另一个进程的现场,不断地在这些进程之间切换 CPU 的注意力。

这称为 进程切换。

Gocon 2014 (29)

进程切换有三个主要开销。

首先,内核需要保护该进程的所有 CPU 寄存器的现场,然后恢复另一个进程的现场。

内核还需要将 CPU 的映射从虚拟内存刷新到物理内存,因为这些映射仅对当前进程有效。

最后是操作系统 上下文切换 Context Switch 的成本,以及 调度函数 Scheduler Function 选择占用 CPU 的下一个进程的开销。

Gocon 2014 (31)

现代处理器中有数量惊人的寄存器。我很难在一张幻灯片上排开它们,这可以让你知道保护和恢复它们需要多少时间。

由于进程切换可以在进程执行的任何时刻发生,因此操作系统需要存储所有寄存器的内容,因为它不知道当前正在使用哪些寄存器。

Gocon 2014 (32)

这导致了线程的出生,这些线程在概念上与进程相同,但共享相同的内存空间。

由于线程共享地址空间,因此它们比进程更轻,因此创建速度更快,切换速度更快。

Gocon 2014 (33)

Goroutine 升华了线程的思想。

Goroutine 是 协作式调度 Cooperative Scheduled <br/> 的,而不是依靠内核来调度。

当对 Go 运行时调度器 Runtime Scheduler 进行显式调用时,goroutine 之间的切换仅发生在明确定义的点上。

编译器知道正在使用的寄存器并自动保存它们。

Gocon 2014 (34)

虽然 goroutine 是协作式调度的,但运行时会为你处理。

Goroutine 可能会给禅让给其他协程时刻是:

  • 阻塞式通道发送和接收。
  • Go 声明,虽然不能保证会立即调度新的 goroutine。
  • 文件和网络操作式的阻塞式系统调用。
  • 在被垃圾回收循环停止后。

Gocon 2014 (35)

这个例子说明了上一张幻灯片中描述的一些调度点。

箭头所示的线程从左侧的 ReadFile 函数开始。遇到 os.Open,它在等待文件操作完成时阻塞线程,因此调度器将线程切换到右侧的 goroutine。

继续执行直到从通道 c 中读,并且此时 os.Open 调用已完成,因此调度器将线程切换回左侧并继续执行 file.Read 函数,然后又被文件 IO 阻塞。

调度器将线程切换回右侧以进行另一个通道操作,该操作在左侧运行期间已解锁,但在通道发送时再次阻塞。

最后,当 Read 操作完成并且数据可用时,线程切换回左侧。

Gocon 2014 (36)

这张幻灯片显示了低级语言描述的 runtime.Syscall 函数,它是 os 包中所有函数的基础。

只要你的代码调用操作系统,就会通过此函数。

entersyscall 的调用通知运行时该线程即将阻塞。

这允许运行时启动一个新线程,该线程将在当前线程被阻塞时为其他 goroutine 提供服务。

这导致每 Go 进程的操作系统线程相对较少,Go 运行时负责将可运行的 Goroutine 分配给空闲的操作系统线程。

Gocon 2014 (37)

在上一节中,我讨论了 goroutine 如何减少管理许多(有时是数十万个并发执行线程)的开销。

Goroutine故事还有另一面,那就是栈管理,它引导我进入我的最后一个话题。

Gocon 2014 (39)

这是一个进程的内存布局图。我们感兴趣的关键是堆和栈的位置。

传统上,在进程的地址空间内,堆位于内存的底部,位于程序(代码)的上方并向上增长。

栈位于虚拟地址空间的顶部,并向下增长。

Gocon 2014 (40)

因为堆和栈相互覆盖的结果会是灾难性的,操作系统通常会安排在栈和堆之间放置一个不可写内存区域,以确保如果它们发生碰撞,程序将中止。

这称为保护页,有效地限制了进程的栈大小,通常大约为几兆字节。

Gocon 2014 (41)

我们已经讨论过线程共享相同的地址空间,因此对于每个线程,它必须有自己的栈。

由于很难预测特定线程的栈需求,因此为每个线程的栈和保护页面保留了大量内存。

希望是这些区域永远不被使用,而且防护页永远不会被击中。

缺点是随着程序中线程数的增加,可用地址空间的数量会减少。

Gocon 2014 (42)

我们已经看到 Go 运行时将大量的 goroutine 调度到少量线程上,但那些 goroutines 的栈需求呢?

Go 编译器不使用保护页,而是在每个函数调用时插入一个检查,以检查是否有足够的栈来运行该函数。如果没有,运行时可以分配更多的栈空间。

由于这种检查,goroutines 初始栈可以做得更小,这反过来允许 Go 程序员将 goroutines 视为廉价资源。

Gocon 2014 (43)

这是一张显示了 Go 1.2 如何管理栈的幻灯片。

G 调用 H 时,没有足够的空间让 H 运行,所以运行时从堆中分配一个新的栈帧,然后在新的栈段上运行 H。当 H 返回时,栈区域返回到堆,然后返回到 G

Gocon 2014 (44)

这种管理栈的方法通常很好用,但对于某些类型的代码,通常是递归代码,它可能导致程序的内部循环跨越这些栈边界之一。

例如,在程序的内部循环中,函数 G 可以在循环中多次调用 H

每次都会导致栈拆分。 这被称为 热分裂 Hot Split 问题。

Gocon 2014 (45)

为了解决热分裂问题,Go 1.3 采用了一种新的栈管理方法。

如果 goroutine 的栈太小,则不会添加和删除其他栈段,而是分配新的更大的栈。

旧栈的内容被复制到新栈,然后 goroutine 使用新的更大的栈继续运行。

在第一次调用 H 之后,栈将足够大,对可用栈空间的检查将始终成功。

这解决了热分裂问题。

Gocon 2014 (46)

值,内联,逃逸分析,Goroutines 和分段/复制栈。

这些是我今天选择谈论的五个特性,但它们绝不是使 Go 成为快速的语言的唯一因素,就像人们引用他们学习 Go 的理由的三个原因一样。

这五个特性一样强大,它们不是孤立存在的。

例如,运行时将 goroutine 复用到线程上的方式在没有可扩展栈的情况下几乎没有效率。

内联通过将较小的函数组合成较大的函数来降低栈大小检查的成本。

逃逸分析通过自动将从实例从堆移动到栈来减少垃圾回收器的压力。

逃逸分析还提供了更好的 缓存局部性 Cache Locality

如果没有可增长的栈,逃逸分析可能会对栈施加太大的压力。

Gocon 2014 (47)

  • 感谢 Gocon 主办方允许我今天发言
  • twitter / web / email details
  • 感谢 @offbymany,@billkennedy\_go 和 Minux 在准备这个演讲的过程中所提供的帮助。

相关文章:

  1. 听我在 OSCON 上关于 Go 性能的演讲
  2. 为什么 Goroutine 的栈是无限大的?
  3. Go 的运行时环境变量的旋风之旅
  4. 没有事件循环的性能

作者简介:

David 是来自澳大利亚悉尼的程序员和作者。

自 2011 年 2 月起成为 Go 的 contributor,自 2012 年 4 月起成为 committer。

联系信息


via: https://dave.cheney.net/2014/06/07/five-things-that-make-go-fast

作者:Dave Cheney 译者:houbaron 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在这篇文章中,我将讨论为什么你需要尝试一下 Go 语言,以及应该从哪里学起。

Go 语言是可能是最近几年里你经常听人说起的编程语言。尽管它在 2009 年已经发布了,但它最近才开始流行起来。

根据 Google 趋势,Go 语言非常流行。

这篇文章不会讨论一些你经常看到的 Go 语言的主要特性。

相反,我想向您介绍一些相当小众但仍然很重要的功能。只有在您决定尝试 Go 语言后,您才会知道这些功能。

这些都是表面上没有体现出来的惊人特性,但它们可以为您节省数周或数月的工作量。而且这些特性还可以使软件开发更加愉快。

阅读本文不需要任何语言经验,所以不必担心你还不了解 Go 语言。如果你想了解更多,可以看看我在底部列出的一些额外的链接。

我们将讨论以下主题:

  • GoDoc
  • 静态代码分析
  • 内置的测试和分析框架
  • 竞争条件检测
  • 学习曲线
  • 反射
  • Opinionatedness
  • 文化

请注意,这个列表不遵循任何特定顺序来讨论。

GoDoc

Go 语言非常重视代码中的文档,所以也很简洁。

GoDoc 是一个静态代码分析工具,可以直接从代码中创建漂亮的文档页面。GoDoc 的一个显著特点是它不使用任何其他的语言,如 JavaDoc、PHPDoc 或 JSDoc 来注释代码中的结构,只需要用英语。

它使用从代码中获取的尽可能多的信息来概述、构造和格式化文档。它有多而全的功能,比如:交叉引用、代码示例,并直接链接到你的版本控制系统仓库。

而你需要做的只有添加一些像 // MyFunc transforms Foo into Bar 这样子的老牌注释,而这些注释也会反映在的文档中。你甚至可以添加一些通过网络界面或者在本地可以实际运行的 代码示例

GoDoc 是 Go 的唯一文档引擎,整个社区都在使用。这意味着用 Go 编写的每个库或应用程序都具有相同的文档格式。从长远来看,它可以帮你在浏览这些文档时节省大量时间。

例如,这是我最近一个小项目的 GoDoc 页面:pullkee — GoDoc

静态代码分析

Go 严重依赖于静态代码分析。例如用于文档的 godoc,用于代码格式化的 gofmt,用于代码风格的 golint,等等。

它们是如此之多,甚至有一个总揽了它们的项目 gometalinter ,将它们组合成了单一的实用程序。

这些工具通常作为独立的命令行应用程序实现,并可轻松与任何编码环境集成。

静态代码分析实际上并不是现代编程的新概念,但是 Go 将其带入了绝对的范畴。我无法估量它为我节省了多少时间。此外,它给你一种安全感,就像有人在你背后支持你一样。

创建自己的分析器非常简单,因为 Go 有专门的内置包来解析和加工 Go 源码。

你可以从这个链接中了解到更多相关内容: GothamGo Kickoff Meetup: Alan Donovan 的 Go 静态分析工具

内置的测试和分析框架

您是否曾尝试为一个从头开始的 JavaScript 项目选择测试框架?如果是这样,你或许会理解经历这种 过度分析 analysis paralysis 的痛苦。您可能也意识到您没有使用其中 80% 的框架。

一旦您需要进行一些可靠的分析,问题就会重复出现。

Go 附带内置测试工具,旨在简化和提高效率。它为您提供了最简单的 API,并做出最小的假设。您可以将它用于不同类型的测试、分析,甚至可以提供可执行代码示例。

它可以开箱即用地生成便于持续集成的输出,而且它的用法很简单,只需运行 go test。当然,它还支持高级功能,如并行运行测试,跳过标记代码,以及其他更多功能。

竞争条件检测

您可能已经听说了 Goroutine,它们在 Go 中用于实现并发代码执行。如果你未曾了解过,这里有一个非常简短的解释。

无论具体技术如何,复杂应用中的并发编程都不容易,部分原因在于竞争条件的可能性。

简单地说,当几个并发操作以不可预测的顺序完成时,竞争条件就会发生。它可能会导致大量的错误,特别难以追查。如果你曾经花了一天时间调试集成测试,该测试仅在大约 80% 的执行中起作用?这可能是竞争条件引起的。

总而言之,在 Go 中非常重视并发编程,幸运的是,我们有一个强大的工具来捕捉这些竞争条件。它完全集成到 Go 的工具链中。

您可以在这里阅读更多相关信息并了解如何使用它:介绍 Go 中的竞争条件检测 - Go Blog

学习曲线

您可以在一个晚上学习所有的 Go 语言功能。我是认真的。当然,还有标准库,以及不同的,更具体领域的最佳实践。但是两个小时就足以让你自信地编写一个简单的 HTTP 服务器或命令行应用程序。

Go 语言拥有出色的文档,大部分高级主题已经在他们的博客上进行了介绍:Go 编程语言博客

比起 Java(以及 Java 家族的语言)、Javascript、Ruby、Python 甚至 PHP,你可以更轻松地把 Go 语言带到你的团队中。由于环境易于设置,您的团队在完成第一个生产代码之前需要进行的投资要小得多。

反射

代码反射本质上是一种隐藏在编译器下并访问有关语言结构的各种元信息的能力,例如变量或函数。

鉴于 Go 是一种静态类型语言,当涉及更松散类型的抽象编程时,它会受到许多各种限制。特别是与 Javascript 或 Python 等语言相比。

此外,Go 没有实现一个名为泛型的概念,这使得以抽象方式处理多种类型更具挑战性。然而,由于泛型带来的复杂程度,许多人认为不实现泛型对语言实际上是有益的。我完全同意。

根据 Go 的理念(这是一个单独的主题),您应该努力不要过度设计您的解决方案。这也适用于动态类型编程。尽可能坚持使用静态类型,并在确切知道要处理的类型时使用 接口 interface 。接口在 Go 中非常强大且无处不在。

但是,仍然存在一些情况,你无法知道你处理的数据类型。一个很好的例子是 JSON。您可以在应用程序中来回转换所有类型的数据。字符串、缓冲区、各种数字、嵌套结构等。

为了解决这个问题,您需要一个工具来检查运行时的数据并根据其类型和结构采取不同行为。 反射 Reflect 可以帮到你。Go 拥有一流的反射包,使您的代码能够像 Javascript 这样的语言一样动态。

一个重要的警告是知道你使用它所带来的代价 —— 并且只有知道在没有更简单的方法时才使用它。

你可以在这里阅读更多相关信息: 反射的法则 — Go 博客.

您还可以在此处阅读 JSON 包源码中的一些实际代码: src/encoding/json/encode.go — Source Code

Opinionatedness(专制独裁的 Go)

顺便问一下,有这样一个单词吗?

来自 Javascript 世界,我面临的最艰巨的困难之一是决定我需要使用哪些约定和工具。我应该如何设计代码?我应该使用什么测试库?我该怎么设计结构?我应该依赖哪些编程范例和方法?

这有时候基本上让我卡住了。我需要花时间思考这些事情而不是编写代码并满足用户。

首先,我应该注意到我完全知道这些惯例的来源,它总是来源于你或者你的团队。无论如何,即使是一群经验丰富的 Javascript 开发人员也很容易发现他们在实现相同的结果时,而大部分的经验却是在完全不同的工具和范例上。

这导致整个团队中出现过度分析,并且使得个体之间更难以相互协作。

嗯,Go 是不同的。即使您对如何构建和维护代码有很多强烈的意见,例如:如何命名,要遵循哪些结构模式,如何更好地实现并发。但你只有一个每个人都遵循的风格指南。你只有一个内置在基本工具链中的测试框架。

虽然这似乎过于严格,但它为您和您的团队节省了大量时间。当你写代码时,受一点限制实际上是一件好事。在构建新代码时,它为您提供了一种更直接的方法,并且可以更容易地调试现有代码。

因此,大多数 Go 项目在代码方面看起来非常相似。

文化

人们说,每当你学习一门新的口语时,你也会沉浸在说这种语言的人的某些文化中。因此,您学习的语言越多,您可能会有更多的变化。

编程语言也是如此。无论您将来如何应用新的编程语言,它总能给你带来新的编程视角或某些特别的技术。

无论是函数式编程, 模式匹配 pattern matching 还是 原型继承 prototypal inheritance 。一旦你学会了它们,你就可以随身携带这些编程思想,这扩展了你作为软件开发人员所拥有的问题解决工具集。它们也改变了你阅读高质量代码的方式。

而 Go 在这方面有一项了不起的财富。Go 文化的主要支柱是保持简单,脚踏实地的代码,而不会产生许多冗余的抽象概念,并将可维护性放在首位。大部分时间花费在代码的编写工作上,而不是在修补工具和环境或者选择不同的实现方式上,这也是 Go 文化的一部分。

Go 文化也可以总结为:“应当只用一种方法去做一件事”。

一点注意事项。当你需要构建相对复杂的抽象代码时,Go 通常会妨碍你。好吧,我会说这是简单的权衡。

如果你真的需要编写大量具有复杂关系的抽象代码,那么最好使用 Java 或 Python 等语言。然而,这种情况却很少。

在工作时始终使用最好的工具!

总结

你或许之前听说过 Go,或者它暂时在你圈子以外的地方。但无论怎样,在开始新项目或改进现有项目时,Go 可能是您或您团队的一个非常不错的选择。

这不是 Go 的所有惊人的优点的完整列表,只是一些被人低估的特性。

请尝试一下从 Go 之旅 来开始学习 Go,这将是一个令人惊叹的开始。

如果您想了解有关 Go 的优点的更多信息,可以查看以下链接:

并在评论中分享您的阅读感悟!

即使您不是为了专门寻找新的编程语言语言,也值得花一两个小时来感受它。也许它对你来说可能会变得非常有用。

不断为您的工作寻找最好的工具!

题图来自 https://github.com/ashleymcnamara/gophers 的图稿


via: https://medium.freecodecamp.org/here-are-some-amazing-advantages-of-go-that-you-dont-hear-much-about-1af99de3b23a

作者:Kirill Rogovoy 译者:imquanquan 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出