Golang开发环境搭建-Vim篇

虽说sublimetext3+gosublime+gocode是目前较为 流行的Golang开发环境组合,但作为一名VIMer,没有一套得心应手的Vim for Golang dev心里总是过不去的。Golang虽然年轻,但即便是从Go 1版本发布(2012年3月28日)算起,掐指算来也有小三年了。全世界的开发者已经为Golang贡献了较为成熟的Vim插件了。有了这些插件,搭建出 一套高效的Golang开发环境还是不难的,网上也有大量的资料可以参考,其中就有vim-go作者自己发表的一篇文章《Go development environment for Vim》。不过看别人 写的与自己搭建体验的还是有大不同的,于是想来想去还是把整个过程记录下来。

一、一个干净的环境

找个干净的基础环境,方便确认每个搭建步骤后的效果:

Ubuntu 14.04 x86_64
vim version 7.4.52
go version go1.4beta1 linux/amd64

再准备一个编辑Go源码的测试源文件:

//hellogolang.go

package main

import "fmt"

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

用于验证每个搭建步骤后的变化。

二、严格按照vim-go的官方说明逐一搭建

Vim-go是当前使用最为广泛的用于搭建Golang开发环境的vim插件,这里我同样使用vim-go作为核心和基础进行环境搭建的。vim-go利 用开源Vim插件管理器安装,gmarik/Vundle.vim是目前被推荐次数更多的Vim插件管理器,超过了pathogen。这里我们 就用vundle来作为Vim的插件管理工具。

1、安装Vundle.vim

Vundle.vim的安装步骤如下:

mkdir ~/.vim/bundle
git clone https://github.com/gmarik/Vundle.vim.git ~/.vim/bundle/Vundle.vim   
                                                                  

创建~/.vimrc文件(如果你没有这个文件的话),在文件顶部添加有关Vundle.vim的配置:

set nocompatible              " be iMproved, required
filetype off                  " required

" set the runtime path to include Vundle and initialize
set rtp+=~/.vim/bundle/Vundle.vim
call vundle#begin()

" let Vundle manage Vundle, required
Plugin 'gmarik/Vundle.vim'

" All of your Plugins must be added before the following line
call vundle#end()            " required
filetype plugin indent on    " required

此时Vim仅安装了Vundle.vim这一个插件。编辑hellogolang.go时与编辑普通文本文件无异,一切都还是Vim的默认属性。

2、安装Vim-go

编辑~/.vimrc,在vundle#beginvundle#end间增加一行:

Plugin 'fatih/vim-go'

在Vim内执行 :P luginInstall

Vundle.vim会在左侧打开一个Vundle Installer Preview子窗口,窗口下方会提示:“Processing 'fatih/vim-go'”,待安装完毕后,提示信息变 成“Done!”。

这时,我们可以看到.vim/bundle下多了一个vim-go文件夹:

$ ls .vim/bundle/
vim-go/  Vundle.vim/

此时,再次编辑hellogolang.go,语法高亮有了, 保存时自动format(利用$GOBIN/gofmt)也有了,但其他高级功能,比如自动import缺失的 package、自动补齐仍然没有,我们还要继续安装一些东东。

3、安装go.tools Binaries

vim-go安装说明中提到所有必要的binary需要先安装好,比如gocode、godef、goimports等。

通过:GoInstallBinaries,这些vim-go依赖的二进制工具将会自动被下载,并被安装到$GOBIN下或$GOPATH/bin下。(这个工具需要依赖git或hg,需要提前安装到你的OS中。)

:GoInstallBinaries的执行是交互式的,你需要回车确认:

vim-go: gocode not found. Installing github.com/nsf/gocode to folder /home/tonybai/go/bin
vim-go: goimports not found. Installing code.google.com/p/go.tools/cmd/goimports to folder /home/tonybai/go/bin/
vim-go: godef not found. Installing code.google.com/p/rog-go/exp/cmd/godef to folder /home/tonybai/go/bin/
vim-go: oracle not found. Installing code.google.com/p/go.tools/cmd/oracle to folder /home/tonybai/go/bin/
vim-go: gorename not found. Installing code.google.com/p/go.tools/cmd/gorename to folder /home/tonybai/go/bin/
vim-go: golint not found. Installing github.com/golang/lint/golint to folder /home/tonybai/go/bin/

vim-go: errcheck not found. Installing github.com/kisielk/errcheck to folder /home/tonybai/go/bin/

不过这些代码多在code.google.com上托管,因此由于众所周知的原因,vim-go的自动安装很可能以失败告终,这样就需要你根据上 面日志中提到的各个工具的源码地址逐一去下载并本地安装。无法搭梯子的,可以通过http://gopm.io 下载相关包。

安装后,$GOBIN下的新增Binaries如下:
-rwxr-xr-x  1 tonybai tonybai  5735552 11??  7 11:03 errcheck*
-rwxr-xr-x  1 tonybai tonybai  9951008 11??  7 10:33 gocode*
-rwxr-xr-x  1 tonybai tonybai  5742800 11??  7 11:07 godef*
-rwxr-xr-x  1 tonybai tonybai  4994120 11??  7 11:00 goimports*
-rwxr-xr-x  1 tonybai tonybai  5750152 11??  7 11:03 golint*
-rwxr-xr-x  1 tonybai tonybai  6381832 11??  7 11:01 gorename*
-rwxr-xr-x  1 tonybai tonybai  2954392 11??  7 10:38 gotags*
-rwxr-xr-x  1 tonybai tonybai  9222856 11??  7 11:01 oracle*

安装好这些Binaries后,我们来看看哪些特性被支持了。

再次编辑hellogolang.go

         - 新起一行输入fmt.,然后ctrl+x, ctrl+o,Vim 会弹出补齐提示下拉框,不过并非实时跟随的那种补齐,这个补齐是由gocode提供的。
    – 输入一行代码:time.Sleep(time.Second),执行:GoImports,Vim会自动导入time包。
    – 将光标移到Sleep函数上,执行:GoDef或命令模式下敲入gd,Vim会打开$GOROOT/src/time/sleep.go中 的Sleep函数的定义。执行:b 1返回到hellogolang.go。
    – 执行:GoLint,运行golint在当前Go源文件上。
    – 执行:GoDoc,打开当前光标对应符号的Go文档。
    – 执行:GoVet,在当前目录下运行go vet在当前Go源文件上。
    – 执行:GoRun,编译运行当前main package。
    – 执行:GoBuild,编译当前包,这取决于你的源文件,GoBuild不产生结果文件。
    – 执行:GoInstall,安装当前包。
    – 执行:GoTest,测试你当前路径下地_test.go文件。
    – 执行:GoCoverage,创建一个测试覆盖结果文件,并打开浏览器展示当前包的情况。
    – 执行:GoErrCheck,检查当前包种可能的未捕获的errors。
    – 执行:GoFiles,显示当前包对应的源文件列表。
    – 执行:GoDeps,显示当前包的依赖包列表。
    – 执行:GoImplements,显示当前类型实现的interface列表。
    – 执行:GoRename [to],将当前光标下的符号替换为[to]。

三、其他插件

到目前为止,我们还有若干特性没能实现,重点是:

    – 实时跟随的代码补齐
    – Code Snippet support

1、安装YCM(Your Complete Me)

在~/.vimrc中添加一行:

Plugin 'Valloric/YouCompleteMe'

保存退出后,再打开~/.vimrc并执行 :P luginInstall

安装完后,下面的提示栏提示:

ycm_client_support.[so|pyd|dll] and ycm_core.[so|pyd|dll] not detected; you need to compile YCM before using it. Read the docs!

似乎YCM是用了C++编写的模块对性能进行优化了,于是需要手工编译YCM的support库。步骤如下:

sudo apt-get install build-essential cmake python-dev
cd ~/.vim/bundle/YouCompleteMe
./install.sh

构建(编译C++很慢,需要耐心的等一会)ok后,再打开hellogolang.go,逐字的实时补全功能就具备了!Cool!

2、安装 UltiSnips

Vim-go默认是用ultisnips引擎插件,但这个插件需要单独安装。

同样,我们利用vundle来安装它,在~/.vimrc中添加一行:

Plugin 'SirVer/ultisnips'

snippet和snippet引擎是分开的。ultisnips是引擎,vim-go的go snippet定义在这里

https://github.com/fatih/vim-go/blob/master/gosnippets/snippets/go.snip

编辑hellogolang.go,按照go.snip中的说明,我们输入func后敲击tab键,我们发现期待的:

func name(params) type {
       
}

并没有出现。反倒是YCM的下拉提示显示在那里让你选择。似乎是ultisnips和YCM的键组合冲突了。ultisnips官方说明也的确如 此。ultisnips默认是用Tab展开snippet的,而YCM中的Tab用来选择补齐项,我们可以通过设置来避免这些。

我们在.vimrc中添加如下setting:

" YCM settings
let g:ycm_key_list_select_completion = ['', '']
let g:ycm_key_list_previous_completion = ['']
let g:ycm_key_invoke_completion = '<C-Space>'

" UltiSnips setting
let g:UltiSnipsExpandTrigger="<tab>"
let g:UltiSnipsJumpForwardTrigger="<c-b>"
let g:UltiSnipsJumpBackwardTrigger="<c-z>"

这样让YCM通过回车和向下的箭头来做list item正向选择,通过向上箭头做反向选择。通过ctrl+space来原地触发补齐提示。

而ultisnips则是用tab做snippet展开,ctrl+b正向切换占位符,ctrl+z反向切换占位符。

3、安装molokai theme

Molokai theme是TextMate的theme的vim port,看着截图挺不错的,于是也安装了一下。

    mkdir ~/.vim/colors
    下载或copy https://github.com /fatih/molokai/blob/master/colors/molokai.vim到~/.vim /colors目录下
    在.vimrc添加一行:colorscheme molokai

四、.vimrc

前面讲到了vim-go有许多命令,在:xx模式下执行多显不便,于是你可以定义一些Mappings,比如:

" set mapleader
let mapleader = ","

" vim-go custom mappings
au FileType go nmap <Leader>s <Plug>(go-implements)
au FileType go nmap <Leader>i <Plug>(go-info)
au FileType go nmap <Leader>gd <Plug>(go-doc)
au FileType go nmap <Leader>gv <Plug>(go-doc-vertical)
au FileType go nmap <leader>r <Plug>(go-run)
au FileType go nmap <leader>b <Plug>(go-build)
au FileType go nmap <leader>t <Plug>(go-test)
au FileType go nmap <leader>c <Plug>(go-coverage)
au FileType go nmap <Leader>ds <Plug>(go-def-split)
au FileType go nmap <Leader>dv <Plug>(go-def-vertical)
au FileType go nmap <Leader>dt <Plug>(go-def-tab)
au FileType go nmap <Leader>e <Plug>(go-rename)

这样我们在命令模式下,输入<,>+<r>就是运行 当前main包,以此类推。

另外下面这个配置使得我们在save file时既可以格式化代码,又可以自动插入包导入语句(或删除不用的包导入语句)。

" vim-go settings
let g:go_fmt_command = "goimports"

到这里,我们的Vim Golang开发环境就基本搭建好了。snippet+实时补齐让你Coding如飞!

五、附录:.vimrc文件

下面是截至目前为止全量.vimrc文件的内容:

set nocompatible              " be iMproved, required
filetype off                  " required
colorscheme molokai

" set the runtime path to include Vundle and initialize
set rtp+=~/.vim/bundle/Vundle.vim
call vundle#begin()

" let Vundle manage Vundle, required
Plugin 'gmarik/Vundle.vim'
Plugin 'fatih/vim-go'
Plugin 'Valloric/YouCompleteMe'

Plugin 'SirVer/ultisnips'

" All of your Plugins must be added before the following line
call vundle#end()            " required
filetype plugin indent on    " required

" set mapleader
let mapleader = ","

" vim-go custom mappings
au FileType go nmap <Leader>s <Plug>(go-implements)
au FileType go nmap <Leader>i <Plug>(go-info)
au FileType go nmap <Leader>gd <Plug>(go-doc)
au FileType go nmap <Leader>gv <Plug>(go-doc-vertical)
au FileType go nmap <leader>r <Plug>(go-run)
au FileType go nmap <leader>b <Plug>(go-build)
au FileType go nmap <leader>t <Plug>(go-test)
au FileType go nmap <leader>c <Plug>(go-coverage)
au FileType go nmap <Leader>ds <Plug>(go-def-split)
au FileType go nmap <Leader>dv <Plug>(go-def-vertical)
au FileType go nmap <Leader>dt <Plug>(go-def-tab)
au FileType go nmap <Leader>e <Plug>(go-rename)

" vim-go settings
let g:go_fmt_command = "goimports"

" YCM settings
let g:ycm_key_list_select_completion = ['', '']
let g:ycm_key_list_previous_completion = ['', '']
let g:ycm_key_invoke_completion = '<C-Space>'

" UltiSnips settings
let g:UltiSnipsExpandTrigger="<tab>"
let g:UltiSnipsJumpForwardTrigger="<c-b>"
let g:UltiSnipsJumpBackwardTrigger="<c-z>"

六、Mac OS X下Vim配置

1、MacVim替换

Mac OS X下的配置方法稍有不同,因为Mac下系统自带的Vim是7.3版本,YCM要求Vim 7.3.584+版本,因此我们需要安装MacVim以替代自带的Vim,目前MacVim最新版本是version 7.4.258,完全满足要求。在这里https://github.com/b4winckler/macvim/releases可以下载到最新的MacVim,下载后的MacVim可以通过如下步骤替换原Vim。

原Vim安装到/usr/bin/vim下。

MacVim解压后如下:

[tony@tonydeair ~/Downloads/MacVim-snapshot-73]$ls
MacVim.app/    README.txt    mvim*

我们执行以下步骤即可完成vim替换工作:

sudo mv /usr/bin/vim /usr/bin/vim.bak //备份一下原vim
cp mvim /usr/local/bin/
sudo ln -s /usr/local/bin/mvim /usr/bin/vim

2、插件安装和配置

按照上面Linux Vim的插件安装步骤和配置方法我们来配置MacVim,配置后,我们发现除了molokai的colorscheme没有生效外,其余插件工作均正常。而所有.go文件打开,均无molokai方案的颜色高亮,甚至连一般的颜色高亮都没有了。经过不断调试,发现了一个解决方法,在~/.vimrc中添加几行代码即可:

syntax on
au BufRead,BufNewFile *.go set filetype=go
colorscheme molokai

但这几行配置代码如果放在~/.vimrc的前面,则UltiSnips会无法工作,我将其移到~/.vimrc文件的末尾,这样就不存在冲突了(看来.vimrc的插件配置的先后顺序会对插件功能的正常使用有影响)。漂亮的molokai colorscheme也会展现出来!

Go语言是如何处理栈的

Go 1.4Beta1刚刚发布,在Go 1.4Beta1中,Go语言的stack处理方式由之前的"segmented stacks"改为了"continuous stacks"。关于Go语言对stack的处理机制、发展历史、存在问题等,CloudFlare的一篇官方blog进行了系统的阐述,这里的内容就是 翻译自CloudFlare的那篇blog:《How Stacks are Handled in Go》。

在CloudFlare,我们使用Go语言实现各种服务和应用。在这篇博文中,我们将带领大家深入挖掘一些Go的某些纷繁复杂的技术细节。

Go语言的重要特性之一是goroutines。它们是代价低廉、协同调度的执行线程,被用于实现各种操作,诸如timeout、生成器、相互竞 争的后端程序。为了使goroutines可以适应更多地任务,我们不仅需要保证每个goroutines的内存最小占用量,还要保证人们可以使 用最低配置将它们启动起来。

为了实现这个目标,Go语言采用了栈管理,这一与其他编程语言类似的方案,但在具体实现层面,又与其他语言有着较大的不同。

一、线程栈(thread stacks)介绍

在我们研究Go的栈处理方式之前,我们先来看看传统语言,比如C是如何进行栈管理的。

当你启动一个C实现的thread时,C标准库会负责分配一块内存作为这个线程的栈。标准库分配这块内存,告诉内核它的位置并让内核处理这个线程 的执行。不过当这块内存不够用时,问题就来了,我们来看一下下面这个函数:

int a(int m, int n) {
    if (m == 0) {
        return n + 1;
    } else if (m > 0 && n == 0) {
        return a(m – 1, 1);
    } else {
        return a(m – 1, a(m, n – 1));
    }
}

这个函数大量使用递归,执行a(4, 5)就会降所有栈内存耗尽。要解决这个问题,你可以调整标准库给线程栈分配的内存块的大小。但是全线提高栈大小意味着每个线程都会提高栈的内存使用量,即 便它们不是大量采用递归方式的。这样一来,你将用光所有内存,即便你的程序还尚未使用栈上的内存。

另外一种可选的解决方法则是为每个线程单独确定栈大小。这样一来你就不得不完成这样的任务:根据每个线程的需要,估算它们的栈内存的大小。这将是 创建线程的难度超出我们的期望。想搞清楚一般情况下一个线程栈需要多少内存是不可行的,即便是通常情况也是非常困难的。

二、Go是如何应对这个问题的

Go运行时会试图按需为goroutine提供它们所需要的栈空间,而不是为每个goroutine分配一个固定大小的栈空间。这样可以把程序员 们从决定栈空间大小的烦心事中解脱了出来。不过Go核心团队正在尝试切换到另外一种方案,这里我将尝试阐述旧方案以及它的缺点,新方案以及为何要 做出如此改变。

三、分段栈(Segmented Stacks)

分段栈(segmented stacks)是Go语言最初用来处理栈的方案。当创建一个goroutine时,Go运行时会分配一段8K字节的内存用于栈供goroutine运行使 用,我们让goroutine在这个栈上完成其任务处理。

当我们用光这8K字节的栈空间后,问题随之而来。为了解决这个问题,每个go函数在函数入口处都会有一小段代码(called prologue),这段代码会检查是否用光了已分配的栈空间,如果用光了,这段代码会调用morestack函数。

morestack函数会分配一段新内存用作栈空间,接下来它会将有关栈的各种数据信息写入栈底的一个struct中(译注:下图中Stack info),包括上一段栈的地址。有点我们拥有了一个新的栈段(stack segment),我们将重启goroutine,从导致栈空间用光的那个函数(译注:下图中的Foobar)开始执行。这就是所谓的“栈分裂 (stack split)”。

下面的栈示意图刚好是我们进行栈分裂后的情形:

在新栈的底部,我们插入了一个栈入口函数lessstack。我们不会调用该函数,设置这个函数就是用于我们从那个导致我们用光栈空间的函数(译 注:Foobar)返回时用的。当那个函数(译注:Foobar)返回时,我们回到lessstack(这个栈帧),lessstack会查找 stack底部的那个struct,并调整栈指针(stack pointer),使得我们返回到前一段栈空间。这样做之后,我们就可以将这个新栈段(stack segment)释放掉,并继续执行我们的程序了。

四、分段栈(Segmented stacks)的问题

分段栈给了我们具备按需伸缩能力的栈。程序员们无需担心计算栈的大小了,启动一个新的goroutine代价低廉并且程序员不会知道栈将增长多 大。

这就是直到目前Go语言处理stack增长的方法,但是这个方法有个瑕疵。那就是栈缩小会是一个相对代价高昂的操作。如果你在一个循环遇到栈分裂 (stack split),你会最有感触。一个函数会增加栈空间,做栈分裂,返回并释放栈段(stack segment)。如果你在一个循环中进行这些,你会付出很大的代价(性能方面)。

这就是所谓的“hot split”问题。它也是Go核心开发组更换到一个新的栈管理方案-栈拷贝(stack copying)的主要原因。

五、栈拷贝(stack copying)

栈拷贝初始阶段与分段栈类似。goroutine在栈上运行着,当用光栈空间,它遇到与旧方案中相同的栈溢出检查。但是与旧方案采用的保留一个返 回前一段栈的link不同,新方案创建一个两倍于原stack大小的新stack,并将旧栈拷贝到其中。这意味着当栈实际使用的空间缩小为原先的 大小时,go运行时不用做任何事情。栈缩小是一个无任何代价的操作。此外,当栈再次增长时,运行时也无需做任何事情,我们只需要重用之前分配的空 闲空间即可。

六、栈是怎么拷贝的

拷贝栈听起来简单,但实际上它是一件有难度的事情。因为Go中栈上的变量都有自己的地址,一旦你拥有指向栈上变量的指针,这种情况下你就无法如你 所愿。当你移动栈时,指向原栈的指针都将变为无效指针。

幸运的是,只有在栈上分配的指针才能指向栈上的地址。这点对于内存安全是极其必要的,否则,程序可能会访问到已不再使用了的栈上的地址。

由于我们需要知道那些需要被垃圾收集器回收的指针的位置,因此我们知道栈上哪些部分是指针。当我们移动栈时,我们可以更新栈里地指针使其指向新的 目标地址,并且所有相关的指针都要被照顾到。

由于我们使用垃圾回收的信息来协助完成栈拷贝,因此所有出现在栈上的函数都必须具备这些信息。但事情不总是这样的。因为Go运行时的大部分代码是 用C编写的,大量的运行时调用没有指针信息可用,这样就无法进行拷贝。一旦这种情况发生,我们又不得不退回到分段栈方案,并接受为其付出的高昂代 价。

这就是当前Go运行时开发者大规模重写Go runtime的原因。那些无法用Go重写的代码,比如调度器和垃圾收集器的内核,将在一个特殊的栈上执行,这个特殊栈的size由runtime开发者 单独计算确定。

除了让栈拷贝成为可能之外,这个方法还会使得我们在未来能够实现出并发垃圾回收等特性。

七、关于虚拟内存

另外一种不同的栈处理方式就是在虚拟内存中分配大内存段。由于物理内存只是在真正使用时才会被分配,因此看起来好似你可以分配一个大内存段并让操 作系统处理它。下面是这种方法的一些问题

首先,32位系统只能支持4G字节虚拟内存,并且应用只能用到其中的3G空间。由于同时运行百万goroutines的情况并不少见,因此你很可 能用光虚拟内存,即便我们假设每个goroutine的stack只有8K。

第二,然而我们可以在64位系统中分配大内存,它依赖于过量内存使用。所谓过量使用是指当你分配的内存大小超出物理内存大小时,依赖操作系统保证 在需要时能够分配出物理内存。然而,允许过量使用可能会导致一些风险。由于一些进程分配了超出机器物理内存大小的内存,如果这些进程使用更多内存 时,操作系统将不得不为它们补充分配内存。这会导致操作系统将一些内存段放入磁盘缓存,这常常会增加不可预测的处理延迟。正是考虑到这个原因,一 些新系统关闭了对过量使用的支持。

八、结论

为了使goroutine使用代价更加低廉,更快速,适合更多task情况,Go开发组做出了很多努力。栈管理只是其中一小部分。如果你想了解更 多关于栈拷贝的细节,可以参考其设计文档。此外,如果你想了解更多有关Go运行 时重写的细节,这里有一个mail list

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! 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