如何从头开始学一门语言?例如自学golang。提到一门语言,最先想到的肯定是语法。语法一定是最基础、最简单的,但不足以掌握一门语言。回想起最早学过c++,工作后用lua和java居多。其实学不同语言的过程是一样的,就好像做事的方法都是一样的,有点万变不离其宗。写这篇博客是想记录下自己从0开始,逐步掌握golang的过程。基础语法就不记录了,先从整体上梳理下golang吧。

 

一、语法特点

  • golang中数据类型有值类型(bool int string)和派生类型(struct interface等等)。值类型通常在栈上分配内存,派生类型通常在堆上分配内存。
  • golang中的函数调用,只有值传递,没有引用传递。但是golang提供了指针,使用指针可以达到引用传参的效果,在函数内部修改参数后可以在外部观察到。一个例子是:在c++中使用数组作为参数,修改数组中某个元素的值,函数结束后修改是生效的,c++这里是引用传参,但golang却不是。golang里函数传参都是值传递。
    c++中支持对指针的加减运算,go不支持。go中,整数和地址不能相互转换,map的value不能取地址。
  • 指针的值是一个地址,通过指针访问数据,首先要访问这个地址值,然后再根据地址值去取最终的数据。
  • 指针相关:*是取值运算符,&是取地址运算符。*也用来声明数组,代码举例
package main
import "fmt"

func main(){
	var num int = 3
	var ptr *int= &num 
	var value = *ptr
	fmt.Println("数字num的值是",num)
	fmt.Println("num的地址是",ptr)
	fmt.Println("对地址取值得到",value)
}
  • 结构体使用一块连续的内存,结构体中的字段按照它们被声明的顺序在这块内存中存储,但是有对齐规则。因此不同的字段声明顺序也影响结构体占用的内存大小,也就是说,结构体中字段声明的顺序是一个可以优化的点,可以减少内存占用
  1. 内存对齐的原因:

    不同计算机的字长可能不同,32位计算机和64位计算机字长不同。cpu每次读取一个字长的数据。

    各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对 数据存放进行对齐,会在存取效率上带来损失。

  2. 内存对齐规则(不同的描述方式是一样的含义)

    第一种描述方式:
    struct内部每个成员按自身大小对齐。
    struct末尾紧贴着一个相同类型的struct,也能够使下一个struct内成员对齐。

    第二中描述方式:
    结构体变量的起始地址能够被其最宽的成员大小整除
    结构体每个成员相对于起始地址的偏移能够被其自身大小整除,如果不能则在前一个成员后面补充字节
    结构体总体大小能够被最宽的成员的大小整除,如不能则在后面补充字节

二、运行时 golang runtime

go是一个编译语言,没有虚拟机,编译后获得的是可以直接运行的二进制文件。go实现跨平台的方式是编译成不同平台的二进制程序。

怎么理解golang runtime呢?可以把它理解成c++里的链接库,当把go源文件编译成可运行程序的时候,goruntime的代码已经集成在程序中了。

goruntime包含以下几部分内容:协程调度器、内存管理、内存回收等等

  1. 调度器模型 GMP 


    其中G表示goroutine协程,M表示machine,P表示process
    golang中通过go语句提交一个协程goroutine,协程是一段可执行的代码,一个计算任务。如果协程的状态是可运行runnable,则会放入全局队列中。全局队列中的goroutine会被分配到不同的局部队列即P(process)中,p的数量通常设置成和cpu核心数相同,p本身不能执行其中的g,而是通过m来执行g。每一个m会绑定一个p,每次从p中获取一个g来执行,g执行结束或者阻塞后再从p中获取下一个g来执行,不断重复这一过程。当m执行g的时候,可能进入阻塞状态的内核调用中,此时m进入阻塞状态,释放自己所持有的p。被释放的p可以关联其他空闲的m继续执行。所以,m的数量大于等于p,p的数量一般等于cpu核心数。
    盗用网上的一张图:

     

  2. 并发模型

    “不要通过共享内存来通信,要通过通信来共享内存”

    java中通常用锁来实现线程同步。通常是一个可重入的排它锁,只有持有锁的线程才可以对目标内存进行写入,其他线程会被阻塞挂起。

    golang中使用协程和通道来实现并发,通道用来传递顺序消息,通道可以有缓冲,可以没有缓冲。不同协程之间的同步通过通道消息来实现。

    goroutine不是线程,协程的调度不会引起线程调度。

  3. 垃圾回收 gc

三、包管理器gopm 

 

 

包管理器 gopm 类似于maven、apt,概念类似:中央仓库、本地仓库

国内下载依赖比较慢,可以设置代理 GOPROXY=https://proxy.golang.org,direct

几个常用命令如下

  • go env 查看环境变量
  • go env -w KEY=VALUE 写入环境变量
  • go get 从网上下载依赖,安装到本地GOPATH目录中
  • go run 编译并运行代码,不会保留编译结果
  • go build 编译代码,编译结果是可执行文件,可以直接在终端运行