Go (Golang) 语言-快速开始

一、go 基础

0. 什么是golang

Golang一般指go。 Go(又称 Golang)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 开发的一种静态强类型、编译型语言。Go 语言语法与 C 相近,但功能上有:内存安全,GC(垃圾回收),结构形态及 CSP-style 并发计算。

1. GOPATH

GOPATH
参考URL: https://www.jianshu.com/p/cf298a0db3fa

从 Go 1.8 版本开始,Go 开发包在安装完成后,将 GOPATH 赋予了一个默认的目录,参见下表
在这里插入图片描述

GOPATH是用于指定你的workspace 的环境变量,它内部目录一般为:
$HOME/go
–bin # 存放编译后的可执行文件
–pkg # 依赖包编译后的*.a文件
–src # 项目源代码的存放路径

使用命令行查看GOPATH信息

go env |grep GOPATH

为什么要配置GOPATH

go 命令依赖一个重要的环境变量:$GOPATH1

配置GOPATH的用意是为了方便项目的部署和构建,以及可以直接使用go get 命令下载第三方的包到自己的项目的src下和相关的执行文件bin目录,和中间文件pkg

1.src 存放源代码(比如:.go .c .h .s等)
2.pkg 编译后生成的文件(比如:.a)
3.bin 编译后生成的可执行文件(为了方便,可以把此目录加入到 P A T H 变量中,如果有多个 g o p a t h ,那么使用 PATH 变量中,如果有多个gopath,那么使用 PATH变量中,如果有多个gopath,那么使用{GOPATH//😕/bin:}/bin添加所有的bin目录)

如果你只是想单独的写个go代码可以不设置GOPATH

2. go程序入口

package main
import "fmt"
/**
  程序入口代码
 */
func main() {
	fmt.Printf("Hello World");
}
  1. 必须是main包: package main
  2. 必须是main方法: func main()
  3. 文件名不一定是:main.go
  4. main方法不支持任何返回值.
  5. 通过os.Exit返回值
  6. main方法不支持传入参数,通过os.Args获取命令行参数

3. go交叉编译

Golang windows下交叉编译的方法
参考URL: https://blog.csdn.net/qq_33512078/article/details/86415727

go 语言有个强大的地方就是 交叉编译。golang的交叉编译要保证golang版本在1.5以上。

用到了两个变量:
GOOS:目标操作系统
GOARCH:目标操作系统的架构

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build hello.go

其中CGO_ENABLED=0的意思是使用C语言版本的GO编译器,参数配置为0的时候就关闭C语言版本的编译器了。自从golang1.5以后go就使用go语言编译器进行编译了。在golang1.9当中没有使用CGO_ENABLED参数发现依然可以正常编译。当然使用了也可以正常编译。比如把CGO_ENABLED参数设置成1,即在编译的过程当中使用CGO编译器,我发现依然是可以正常编译的。
实际上如果在go当中使用了C的库,比如import "C"默认使用go build的时候就会启动CGO编译器,当然我们可以使用CGO_ENABLED=0来控制go build是否使用CGO编译器。

编译linux
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build

编译windows
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build

编译mac
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go

直接执行命令即可,go交叉编译就是这么简单。

在xx.go所在的的文件夹下按sheet+鼠标右键在dos下打开,执行下面的命令

set GOARCH=amd64

set GOOS=linux

go build xx.go

会生成一个没有后缀的xx二进制文件

三、go常用命令

Go语言中包含了大量用于处理Go语言代码的命令和工具。其中,go命令就是最常用的一个,它有许多子命令。这些子命令都拥有不同的功能,如下所示。
build:用于编译给定的代码包或Go语言源码文件及其依赖包。
 clean:用于清除执行其他go命令后遗留的目录和文件。
 doc:用于执行godoc命令以打印指定代码包。
 env:用于打印Go语言环境信息。
 fix:用于执行go tool fix命令以修正给定代码包的源码文件中包含的过时语法和代码调用。
 fmt:用于执行gofmt命令以格式化给定代码包中的源码文件。
 get:用于下载和安装给定代码包及其依赖包(提前安装git或hg)。
 list:用于显示给定代码包的信息。
 run:用于编译并运行给定的命令源码文件。
 install:编译包文件并编译整个程序。
 test:用于测试给定的代码包。
 tool:用于运行Go语言的特殊工具。
 version:用于显示当前安装的Go语言的版本信息。

go get 命令

get:用于下载和安装给定代码包及其依赖包(提前安装git或hg)。

  • 用于从远程代码仓库(如Github)上下载并安装代码包
    -支持的代码版本控制系统有:Git、Mercurial(hg)、SVN、Bazaar
  • 指定的代码包会被下载到$GOPATH中包含的第一个工作区的src目录中

常用标记
标记 描述
-d 只执行下载动作,而不执行安装动作
-u 利用网络来更新已有的代码包及其依赖包
-fix 在下载代码包后先执行修正动作(版本兼容问题),而后再进行编译和安装

demo

$ go get -u -d github.com/ipfs/go-ipfs

$ cd $GOPATH/src/github.com/ipfs/go-ipfs
$ make install

Go语言的代码被托管于 Github.com 网站,该网站是基于 Git 代码管理工具的,很多有名的项目都在该网站托管代码。其他类似的托管网站还有 code.google.com、bitbucket.org 等。

这些网站的项目包路径都有一个共同的标准,参见下图所示
在这里插入图片描述
go get+ 远程包
获取前,请确保 GOPATH 已经设置。Go 1.8 版本之后,GOPATH 默认在用户目录的 go 文件夹下。

解决go get无法下载被墙的包

解决go get无法下载被墙的包
参考URL: https://www.jianshu.com/p/81fa51c4125e

  1. 使用gopm代替go下载
    //使用gopm(Go Package Manager)代替go下载,是go上的包管理工具,十分好用
    //1. 下载安装gopm
    go get -u github.com/gpmgo/gopm
    //2. 使用gopm安装被墙的包
    gopm get github.com/Shopify/sarama

    经过测试,go get -u github.com/gpmgo/gopm 也执行不下去!!!

  2. 使用 $GOPROXY

    当我们使用go的时候,go默认会直接从代码库中去下载所需的相关依赖,GOPROXY 这个环境变量可以让我们控制自己从哪里去下载源代码,如果 GOPROXY 没有设置,go 会直接从代码库下载相关依赖代码。如果你像下面这样设置了这个环境变量,那么你就会通过 goproxy.io 下载所有的源代码。

    export GOPROXY=http://mirrors.aliyun.com/goproxy/

    你可以通过置空这个环境变量来关闭,export GOPROXY=

用过go的同学都知道go get …… 是一个漫长的过程,既然python 有pip国内镜像,node 也有国内镜像,那go是否有类似的镜像呢?确实有go mod + goproxy 可参考http://mirrors.aliyun.com/goproxy/

  1. 在环境变量中加入GOPROXY值为https://mirrors.aliyun.com/goproxy/

  2. 新建一个文件夹test

  3. 在test下执行go mod init test

  4. 新建main.go
    写入代码

    package main

    import “github.com/ipfs/go-ipfs”

    func main() {
    app := iris.New()
    app.Get(“/”, func(ctx iris.Context){})
    app.Run(iris.Addr(“:8080”))
    }

  5. go run main.go 就可以感受到飞速的get依赖包了。

四、go语言入门

go语言入门(1)
参考URL: https://www.cnblogs.com/xdyixia/p/11847424.html

go程序开发注意事项

1)Go源文件以"go”为扩展名。
2)Go 应用程序的执行入口是main()函数。
3)Go 语言严格区分大小写。
4)Go方法由一条条语句构成,每个语句后不需要分号(Go语言会在每行后自动加分号),这也体现出Golang的简洁性。
5)Go编译器是一行行进行编译的,因此我们一行就写一条语句,不能把多条语句写在同一个,否则报错
6)go语言定义的变量或者import的包如果没有使用到,代码不能编译通过。

go命名规范

Go中的命名规范
参考URL: https://www.cnblogs.com/rickiyang/p/11074174.html

  1. Go是一门区分大小写的语言

任何需要对外暴露的名字必须以大写字母开头,不需要对外暴露的则应该以小写字母开头。

  • 当命名(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Analysize,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);
  • 命名如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private )
  1. 包名称
    保持package的名字和目录保持一致,尽量采取有意义的包名,简短,有意义,尽量和标准库不要冲突。包名应该为小写单词,不要使用下划线或者混合大小写。

  2. 文件命名
    尽量采取有意义的文件名,简短,有意义,应该为小写单词,使用下划线分隔各个单词。

approve_service.go

go定义变量

Go语言的变量声明的标准格式为:

var 变量名 变量类型

简短格式:

除 var 关键字外,还可使用更加简短的变量定义和初始化语法。
名字 := 表达式
需要注意的是,简短模式(short variable declaration)有以下限制:

  • 定义变量,同时显式初始化。
  • 不能提供数据类型。
  • 只能用在函数内部。

声明数组
var variable_name [SIZE] variable_type
例如以下定义了数组 balance 长度为 10 类型为 float32:

var balance [10] float32

初始化数组
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
初始化数组中 {} 中的元素个数不能大于 [] 中的数字。
如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小:

 var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

go语言中&和*区别

先构建一个Rect类型 :
在这里插入图片描述1. &是取地址符号, 取到Rect类型对象的地址
在这里插入图片描述
2. *可以表示一个变量是指针类型(r是一个指针变量):
在这里插入图片描述3.*也可以表示指针类型变量所指向的存储单元 ,也就是这个地址所指向的值
在这里插入图片描述
4.查看这个指针变量的地址 , 基本数据类型直接打印地址
在这里插入图片描述

Go语言指针

[推荐-原文写的全面详细]Go语言指针详解,看这一篇文章就够了
参考URL: http://c.biancheng.net/view/21.html

与 Java 和 .NET 等编程语言不同,**Go语言为程序员提供了控制数据结构指针的能力,但是,并不能进行指针运算。**Go语言允许你控制特定集合的数据结构、分配的数量以及内存访问模式,这对于构建运行良好的系统是非常重要的。

指针(pointer)在Go语言中可以被拆分为两个核心概念:

  • 类型指针,允许对这个指针类型的数据进行修改,传递数据可以直接使用指针,而无须拷贝数据,类型指针不能进行偏移和运算。
  • 切片,由指向起始元素的原始指针、元素数量和容量组成。

受益于这样的约束和拆分,Go语言的指针类型变量即拥有指针高效访问的特点,又不会发生指针偏移,从而避免了非法修改关键性数据的问题。同时,垃圾回收也比较容易对不会发生偏移的指针进行检索和回收。

切片比原始指针具备更强大的特性,而且更为安全。切片在发生越界时,运行时会报出宕机,并打出堆栈,而原始指针只会崩溃。

package main
import (
    "fmt"
)
func main() {
    var cat int = 1
    var str string = "banana"
    fmt.Printf("%p %p", &cat, &str)
}

运行结果:
0xc042052088 0xc0420461b0

代码说明如下:
第 8 行,声明整型变量 cat。
第 9 行,声明字符串变量 str。
第 10 行,使用 fmt.Printf 的动词%p打印 cat 和 str 变量的内存地址,指针的值是带有0x十六进制前缀的一组数据。

提示:变量、指针和地址三者的关系是,每个变量都拥有地址,指针的值就是地址。

从指针获取指针指向的值
当使用&操作符对普通变量进行取地址操作并得到变量的指针后,可以对指针使用*操作符,也就是指针取值,代码如下。

package main
import (
    "fmt"
)
func main() {
    // 准备一个字符串类型
    var house = "Malibu Point 10880, 90265"
    // 对字符串取地址, ptr类型为*string
    ptr := &house
    // 打印ptr的类型
    fmt.Printf("ptr type: %T\n", ptr)
    // 打印ptr的指针地址
    fmt.Printf("address: %p\n", ptr)
    // 对指针进行取值操作
    value := *ptr
    // 取值后的类型
    fmt.Printf("value type: %T\n", value)
    // 指针取值后就是指向变量的值
    fmt.Printf("value: %s\n", value)
}

运行结果:
ptr type: *string
address: 0xc0420401b0
value type: string
value: Malibu Point 10880, 90265

代码说明如下:
第 10 行,准备一个字符串并赋值。
第 13 行,对字符串取地址,将指针保存到变量 ptr 中。
第 16 行,打印变量 ptr 的类型,其类型为 *string。
第 19 行,打印 ptr 的指针地址,地址每次运行都会发生变化。
第 22 行,对 ptr 指针变量进行取值操作,变量 value 的类型为 string。
第 25 行,打印取值后 value 的类型。
第 28 行,打印 value 的值。

取地址操作符&和取值操作符*是一对互补操作符,*&取出地址,根据地址取出地址指向的值。

变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:

  • 对变量进行取地址操作使用&操作符,可以获得这个变量的指针变量。
  • 指针变量的值是指针地址。
    -对指针变量进行取值操作使用*操作符,可以获得指针变量指向的原变量的值。

Go语言 type关键字

type有几种用法:定义结构体,定义接口, 类型别名, 类型定义, 类型开关

定义结构体
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。类似Java 的类,我们可以把Go中的struct看作是不支持继承行为的轻量级的“类”。

//定义一个 Books结构体
type Books struct {
   title string
   author string
   subject string
   book_id int
}

//结构体内内嵌匿名成员变量定义
func main() {
   p := person{"abc",12}
   fmt.Println(p.string,p.int)
}

type person struct {
   string
   int
}

定义接口

//定义电话接口
type Phone interface {
   call()
}

自定义类型

type name string   // 使用 type 基于现有基础类型,结构体,函数类型创建用户自定义类型。

go import用法

Go语言基础语法(import)-6
参考URL: https://www.jianshu.com/p/9e9a5b5bccd0

import "fmt"最常用的一种形式

import "./test"导入同一目录下test包中的内容

import f "fmt"导入fmt,并给他启别名f
别名操作顾名思义我们可以把包命名成另一个我们用起来容易记忆的名字

import . “fmt”,将fmt启用别名".",这样就可以直接使用其内容,而不用再添加fmt,如fmt.Println可以直接写成Println

import _ “fmt” 表示不使用该包,而是只是使用该包的init函数,并不显示的使用该包的其他内容。注意:这种形式的import,当import时就执行了fmt包中的init函数,而不能够使用该包的其他函数。

demo:

import io "fmt" //引用fmt这个包时,名字重命名为io
import _ "os"    //引用os这个包,但是不调用,其实就是引用它的init函数

import原理
在这里插入图片描述
先初始化依赖包,包中内容的初始化为 常量&变量->init()函数->main(主程文件)中的main()函数

go mod的使用 go.mod文件

go mod 是什么?

在go语言1.11版本之前,没有modules机制,所有软件包都在安装在 G O P A T H / s r c 目录下。不同项目如果引用了同一个软件包的不同版本,就会造成编译麻烦。修改 GOPATH/src目录下。不同项目如果引用了同一个软件包的不同版本,就会造成编译麻烦。修改 GOPATH/src目录下。不同项目如果引用了同一个软件包的不同版本,就会造成编译麻烦。修改GOPATH变量是当时一种比较简单的解决方案。

go.mod是Golang1.11版本新引入的官方包管理工具用于解决之前没有地方记录依赖包具体版本的问题,方便依赖包的管理。

按照当前的趋势估计 go 之后的版本都是用 go mod 来管理了,gopath 这种模式感觉会被淘汰。

Modules和传统的GOPATH不同,不需要包含例如src,bin这样的子目录,一个源代码目录甚至是空目录都可以作为Modules,只要其中包含有go.mod文件。

使用go mod 管理项目,就不需要非得把项目放到GOPATH指定目录下,你可以在你磁盘的任何位置新建一个项目。

使用go mod ,利用Go 的 module 特性,你再也不需要关心GOPATH了(当然GOPATH变量还是要存在的,但只需要指定一个目录,而且以后就不用我们关心了), 你可以任性的在你的硬盘任何位置新建一个Golang项目了。

如果想实现一键分发编译包,通过go mod vendor指令,将依赖包从$GOPATH/pkg/mod目录拷贝至当前项目目录下的vendor目录中。将目录打包,直接分发即可,当然这限同类平台。

go mod 使用步骤

告别GOPATH,快速使用 go mod(Golang包管理工具)
参考URL: https://www.jianshu.com/p/bbed916d16ea

在当前目录下,命令行运行 go mod init + 模块名称 初始化模块

然后在 D:\test\xxx里打开终端执行命令: go mod init xxx(go mod init 后面需要跟一个名字,我这里叫xxx)

运行完之后,会在当前目录下生成一个go.mod文件,这是一个关键文件,之后的包的管理都是通过这个文件管理。

包含go.mod文件的目录也被称为模块根,也就是说,go.mod 文件的出现定义了它所在的目录为一个模块。

执行上述命令之后,其实你已经可以开发编译运行此项目了。

go.sum 是记录所依赖的项目的版本的锁定。除了go.mod之外,go命令还维护一个名为go.sum的文件,其中包含特定模块版本内容的预期加密哈希。go.sum 不需要手工维护,所以可以不用太关注。

go命令使用go.sum文件确保这些模块的未来下载检索与第一次下载相同的位,以确保项目所依赖的模块不会出现意外更改,无论是出于恶意、意外还是其他原因。 go.mod和go.sum都应检入版本控制。

go context 包

go context 包
参考URL: https://www.jianshu.com/p/789177f4ff8e

defer函数

defer 函数它在声明时不会立刻去执行,而是在函数 return 后去执行的。
它的主要应用场景有异常处理、记录日志、清理数据、释放资源 等等。

匿名函数结合使用

package main //必须
 
import "fmt"
 
func main() {
    a := 10
    b := 20
 
    defer func() {
        fmt.Printf("a = %d, b = %d\n", a, b)
    }() //()代表调用此匿名函数
        <br>        //先执行外部的,再执行内部的
    a = 111
    b = 222
    fmt.Printf("外部:a = %d, b = %d\n", a, b)
}

const 定义常量

Go语言中的常量使用关键字 const 定义,用于存储不会改变的数据,常量是在编译时被创建的,即使定义在函数内部也是如此,并且只能是布尔型、数字型(整数型、浮点型和复数)和字符串型。由于编译时的限制,定义常量的表达式必须为能被编译器求值的常量表达式。

批量声明多个常量

const (
	fsAPI           = "api"
	fsAPIToken      = "token"
	fsConfig        = "config.toml"
	fsStorageConfig = "storage.json"
	fsDatastore     = "datastore"
	fsLock          = "repo.lock"
	fsKeystore      = "keystore"
)


type RepoType int

const (
	_                 = iota // Default is invalid
	FullNode RepoType = iota
	StorageMiner
	Worker
)

iota 常量生成器
常量声明可以使用 iota 常量生成器初始化,**它用于生成一组以相似规则初始化的常量,但是不用每行都写一遍初始化表达式。**在一个 const 声明语句中,在第一个声明的常量所在的行,iota 将会被置为 0,然后在每一个有常量声明的行加一。

【示例 1】首先定义一个 Weekday 命名类型,然后为一周的每天定义了一个常量,从周日 0 开始。在其它编程语言中,这种类型一般被称为枚举类型。

type Weekday int

const (
    Sunday Weekday = iota
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
)

周日将对应 0,周一为 1,以此类推。

Go语言中new()和 make()

Go语言中new()和 make()的区别详解
参考URL: https://blog.csdn.net/cut001/article/details/80506710
Go内建函数make及切片slice、映射map详解
参考URL: https://www.jianshu.com/p/f01841004810

  • new
    new 是内建函数,它的定义也很简单:
    func new(Type) *Type

内建函数 new 用来分配内存,它的第一个参数是一个类型,不是一个值,它的返回值是一个指向新分配类型零值的指针。

  • make
    官方文档对于它的描述是:

    内建函数 make 用来为 slice,map 或 chan 类型分配内存和初始化一个对象(注意:只能用在这三种类型上)

    make 的作用是为 slice,map 或 chan 初始化并返回引用(T)。

    通道channel
    创建方式make(chan type, cap),其中type表示通道数据类型,cap表示缓存容量

    //创建有缓存通道
    ch := make(chan int, 10)
    //创建无缓存通道
    ch := make(chan int)
    

make和new对比

  1. 二者都是内存的分配(堆上),但是make只用于slice、map以及channel的初始化(非零值);而new用于类型的内存分配,并且内存置为零
  2. make返回的还是这三个引用类型本身;而new返回的是指向类型的指针
  3. new不常用,通常都是采用短语句声明以及结构体的字面量达到我们的目的,比如:
    i:=0
    u:=user{}
    
  4. make函数是无可替代的

Channel类型/Go语言 Channel <- 箭头操作符 (chan int)

go chan 类型用法
参考URL: https://blog.csdn.net/u011714033/article/details/90085639

Go语言 Channel <- 箭头操作符 详解
参考URL: https://studygolang.com/articles/12745?fr=sidebar

Channel是Go中的一个核心类型,你可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯(communication)。

它的操作符是箭头 <- 。

ch <- v // 发送值v到Channel ch中
v := <-ch // 从Channel ch中接收数据,并将数据赋值给v

Channel类型

ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .

它包括三种类型的定义。可选的<-代表channel的方向。如果没有指定方向,那么Channel就是双向的,既可以接收数据,也可以发送数据。

chan T          // 可以接收和发送类型为 T 的数据
chan<- float64  // 只可以用来发送 float64 类型的数据
<-chan int      // 只可以用来接收 int 类型的数据

<-总是优先和最左边的类型结合。(The <- operator associates with the leftmost chan possible)

demo

	//双向型 chan, 零值 nil
	var ch chan int
	//输入型 chan->
	var ci chan<- int
	//输出型 <-chan
	var co <-chan int

使用make初始化Channel,并且可以设置容量:
make(chan int, 100)

容量(capacity)代表Channel容纳的最多的元素的数量,代表Channel的缓存的大小。
如果没有设置容量,或者容量设置为0, 说明Channel没有缓存,只有sender和receiver都准备好了后它们的通讯(communication)才会发生(Blocking)。如果设置了缓存,就有可能不发生阻塞, 只有buffer满了后 send才会阻塞, 而只有缓存空了后receive才会阻塞。一个nil channel不会通信。

Channel可以作为一个先入先出(FIFO)的队列,接收的数据和发送的数据的顺序是一致的。

go语言 go关键字 协程

理解golang中关键字-go
参考URL: https://blog.csdn.net/reigns_/article/details/102912831
深入浅出Golang关键字"go"
参考URL: https://studygolang.com/articles/16469?fr=sidebar
[推荐-写的很不错]《快学 Go 语言》第 11 课 —— 千军万马跑协程
参考URL: http://blog.itpub.net/31561269/viewspace-2222525/

go关键字定义了golang的并发编程goroutine。

goroutine是建立在线程上的轻量级的抽象,它允许我们以非常低的代价在同一个地址空间中并行的执行多个函数或者方法,相比于线程,它的创建和销毁代价小很多,并且它的调度室独立于线程的。在golang中使用go关键字创建一个goroutine。

使用go关键字放在函数前,这样就定义了一个goroutine。

package main

import (
	"fmt"
	"runtime"
)

func foo() {
	for i := 0; i < 50; i++ {
		go func() {
			fmt.Print(i, " ")

		}()
	}
	runtime.Gosched()

}

func main() {
	foo()
}

上示代码中定义的foo函数中的print函数启用了并发编程,这个函数调用时是并发执行的,**goroutine默认使用电脑的所有核心,其他核在进行go关键字后面的函数运算,但是由于程序执行完main函数结束后立刻结束,因此不确定其他核的函数有没有执行完毕,**也就造成了每次运行打印的数字个数不一样。

总结:主协程运行结束,其它协程就会立即消亡,不管它们是否已经开始运行。

go func(){}()
go func() {

.....

}()

以并发的方式调用匿名函数func

golang 同步等待所有协程执行完毕sync WaitGroup

golang 同步等待所有协程执行完毕sync WaitGroup
golang的sync的包有一个功能WaitGroup

作用:

阻塞主线程的执行,直到所有的goroutine执行完成,说白了就是必须同步等待所有异步操作完成!!!

三个方法:

Add:添加或者减少等待goroutine的数量

Done:相当于Add(-1)

Wait:执行阻塞,直到所有的WaitGroup数量变成0

package main
 
import (
    "fmt"
    "sync"
    "time"
)
 
func main() {
    var wg sync.WaitGroup
 
    for i := 0; i < 5; i = i + 1 {
        wg.Add(1)
        go func(n int) {
            // defer wg.Done()
            defer wg.Add(-1)
            EchoNumber(n)
        }(i)
    }
 
    wg.Wait()
}
 
func EchoNumber(i int) {
    time.Sleep(3e9)
    fmt.Println(i)
}

原来执行脚本,都是使用time.Sleep,用一个估计的时间等到子线程执行完。WaitGroup很好。虽然chanel也能实现,但是觉得如果涉及不到子线程与主线程数据同步,这个感觉不错。

Golang 并行运算以及time/sleep.go

Golang 并行运算以及time/sleep.go
参考URL: https://www.jianshu.com/p/d749ca34780c

go func() // 可以启动一个协程
可以同时运行多个协程,协程之间通讯使用channel(通道)来进行协程间通信
channel的定义如下,
c := chan string //意味着这是一个string类型的通道

channel可以理解为一个队列,先进者先出,但是它只能占有一个元素的位置,我们可以义如下方法添加/读取元素,<- 是一个运算符

c <- "hello"  //把"hello"加入到c通道中
msg := <- c // 将c中的"hello"取出到msg里

select 可以同时监听多个channel:
在并行运算时我们可能需要从多个通道读取消息,此时我们用到

for{
  select{
    case msg1 := channel_1:
      fmt.Println(msg1)
    case msg2 := channel_2:
      fmt.Println(msg2)
  }
}

此时如果channel_1和channel_2两个通道都一直没有消息,那么该协程将会被阻塞在select语句,直到channel_1或者channel_2有消息为止。

for

无限循环:

package main

import "fmt"

func main() {
        sum := 0
        for {
            sum++ // 无限循环下去
        }
        fmt.Println(sum) // 无法输出
}

Go 语言 select 语句

Golang 协程正确的使用方法
参考URL: http://www.dahouduan.com/2017/09/20/golang-goroutines/

select 是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。

Go里面提供了一个关键字select,通过select可以监听channel上的数据流动

select的用法与switch语言非常类似,由select开始一个新的选择块,每个选择条件由case语句来描述。

与switch语句相比, select有比较多的限制,其中最大的一条限制就是每个case语句里必须是一个IO操作,

select 随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。

以下描述了 select 语句的语法:

  • 每个 case 都必须是一个通信
  • 所有 channel 表达式都会被求值
  • 所有被发送的表达式都会被求值
  • 如果任意某个通信可以进行,它就执行,其他被忽略。
  • 如果有多个 case 都可以运行,Select 会随机公平地选出一个执行。其他不会执行。
    否则:
    如果有 default 子句,则执行该语句。
    如果没有 default 子句,select 将阻塞,直到某个通信可以运行;Go 不会重新对 channel 或值进行求值。
//select基本用法
select {
case <- chan1:
// 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
// 如果成功向chan2写入数据,则进行该case处理语句
default:
// 如果上面都没有成功,则进入default处理流程

如果有一个或多个IO操作可以完成,则Go运行时系统会随机的选择一个执行,否则的话,如果有default分支,则执行default分支语句,如果连default都没有,则select语句会一直阻塞,直到至少有一个IO操作可以进行。添加了 default 语句,表示当所有的 channel 都取不到数据时,默认执行 default 后面的语句。

总结: select 可以同时监听多个channel。select不会在nil的通道上进行等待

go语言 定义函数的方式、定义方法

golang 函数定义
参考URL: https://www.sunzhongwei.com/golang-function-definition

go 语言中函数的声明方式为:
func name(parameter-list) (result-list) { body }

其中 func 是关键字,name 是函数名字,parameter-list 是参数列表,result-list 是返回结果列表, body 是函数体 。

func add(x int, y int) int {
	return x + y
}
  • 参数的类型在变量名后面
  • 返回类型也在后面
    如果多个参数的类型一致,可以省略前面的类型
    例如,上面的函数可以简写为
func add(x, y int) int {
	return x + y
}

返回多个值的函数

func swap(x, y string) (string, string) {
	return y, x
}

golang func 函数名前的参数
Go基础:函数声明之方法接受者(函数名之前括号中的内容)
参考URL: http://www.iamlintao.com/6976.html

函数声明之方法接受者(函数名之前括号中的内容)

这里的(t *type)其实就是C#中 type类型的扩展方法

可以在type类型实例中调用该方法 (type).f()

在go语言中,没有类的概念但是可以给类型(结构体,自定义类型)定义方法。

所谓方法就是定义了接受者的函数,方法和函数只差了一个,那就是方法在 func 和标识符之间多了一个参数。接受者定义在func关键字和函数名之间。

有了对方法及接受者的简单认识之后,接下来主要谈一下接受者的类型问题。

接收者有两种,一种是值接收者,一种是指针接收者。顾名思义,值接收者,是接收者的类型是一个值,是一个副本,方法内部无法对其真正的接收者做更改;指针接收者,接收者的类型是一个指针,是接收者的引用,对这个引用的修改之间影响真正的接收者。

何为[]byte? go字节数组

首先在go里面,byte是uint8的别名。而slice结构在go的源码中src/runtime/slice.go定义:

type Ticket struct {
	VRFProof []byte
}

golang中的空结构体 channel := make(chan struct{})

golang中的空结构体 channel := make(chan struct{})
参考URL: https://www.jianshu.com/p/2b35c1543c7c

特定:
省内存,尤其在事件通信的时候。
struct零值就是本身,读取close的channel返回零值

使用场景

  • 首先事件通知,可以通过写入 通知其他协程,但是只能通知一个。
channel := make(chan struct{})
go func() {
    // ... do something
    channel <- struct{}{}
}()
fmt.Println(<-channel)
  • 和close进行配合,通知所有相关协程。
    在读入被close的channel返回零值,正常的协程是读取不到这个close的。
    close之后,所有协程都可以读到。

比较经典的例子就是用于stopChan作为停止channel通知所有协程。
在下面的例子中 我们可以通过s.Stop()通知所有的serverHandler协程停止工作,并且等待他们正常退出。

type Server struct {
    serverStopChan chan struct{}
    stopWg         sync.WaitGroup
}
func (s *Server) Stop() {
    if s.serverStopChan == nil {
        panic("gorpc.Server: server must be started before stopping it")
    }
    close(s.serverStopChan)
    s.stopWg.Wait()
    s.serverStopChan = nil
}
func serverHandler(s *Server){
    for {
        select {
        case <-s.serverStopChan:
            return
        default:
            // .. do something
        }
    }
}

go map的定义和使用 键值对存储

定义map var m map[string]int //定义map
初始化map m = make(map[string]int) //初始化map
修改map中ok 的值 m[“ok”] =123
删除元素 delete(m, “Answer”) 删除key=Answer的元素

go sync.Mutex

Go的sync/mutex实现
参考URL: https://segmentfault.com/a/1190000000506960

sync/mutex是Go语言底层基础对象之一,用于构建多个goroutine间的同步逻辑,因此被大量高层对象所使用。

其工作模型类似于Linux内核的futex对象,具体实现极为简洁,性能也有保证。

特殊变量 下划线 (_)

在golang中有个特殊的变量,那就是下划线,将变量赋值给下划线,表示丢弃这个值,写法如下:

var _ = “hello”
那么下划线这个变量能否被引用呢?答案是:不能。

为什么要使用下划线变量?

golang的函数可以返回多个值,有些场景中,我们只需要其中一个返回值,其余返回值用不上,那么我们就可以将用不上的返回值赋值给下划线变量。 如果不赋值给下划线变量,那么就需要定义变量来接收这些值,但是在golang中定义了却又没有使用的变量,无法通过编译,这些无用的返回值给编码造成了不必要的麻烦。

注意事项:

**在import导入包时,可以在导入包的前边加上下划线,表示只执行这个包中的init函数。**那么导入包时的下划线和变量定义中的下划线是否含义相同?答案是:不同。尽管都是用下划线这个符号来表示,都有丢弃值的操作,但是使用场合不同,意义就不相同,不能混为一谈。

golang 自定义接口 和 实现接口

接口代表一种调用契约,是多个方法声明的集合。

在动态语言中,接口(interface)也被称为协议(protocol)。准备交互的双方,共同遵守事先约定的规则,使得在无须知道对方身份的情况下进行写作。接口要实现的是做什么,而不关心怎么做,谁来做。

golang中的三个点 ‘…’ 的用法

原文链接:https://blog.csdn.net/jeffrey11223/article/details/79166724

…’ 其实是go的一种语法糖。
它的第一个用法主要是用于函数有多个不定参数的情况,可以接受多个不确定数量的参数。
第二个用法是slice可以被打散进行传递

func test1(args ...string) { //可以接受任意个string参数
    for _, v:= range args{
        fmt.Println(v)
    }
}

func main(){
var strss= []string{
        "qwr",
        "234",
        "yui",
        "cvbc",
    }
    test1(strss...) //切片被打散传入
}

golang 打印变量类型

直接使用reflect的TypeOf方法就可以了
模块是: “reflect”

fmt.Println(reflect.TypeOf(var)) 

五、其他

工作中常见通用问题总结

exec: “gcc”: executable file not found in %PATH%

Go语言的cgo时用到的GCC编译器
参考URL: https://www.pianshen.com/article/7614258292/
[转]MingGW64 多个版本区别(silj, seh)
参考URL: https://www.cnblogs.com/fanbi/p/10309800.html

编译go,报错:exec: “gcc”: executable file not found in %PATH%

原因:如果go代码里使用了cgo,那么编译的时候电脑上必须按照gcc才能正确编译,系统没有安装gcc源码编译工具。
解决方法: 下载安装MinGW

优先尝试mingw-w64-install.exe
如果网络错误,则直接下载上面的压缩包x86_64-win32-sjlj,解压到指定目录即可,如在D盘创建文件夹,并解压

pkg-config: exec: “pkg-config”: executable file not found in %PATH%

PKG-CONFIG补充
参考URL: https://www.jianshu.com/p/d060030ef2a2

pkg-config本身是一个linux下的命令,其功能是用于获得某一个库/模块的所有编译相关的信息。

go 异常panic和恢复recover用法

Go语言宕机恢复(recover)——防止程序崩溃
参考URL: http://c.biancheng.net/view/64.html
Go语言 异常panic和恢复recover用法
参考URL: https://www.jianshu.com/p/0cbc97bd33fb

recover 是一个Go语言的内建函数,可以让进入宕机流程中的 goroutine 恢复过来,recover 仅在延迟函数 defer 中有效,在正常的执行过程中,调用 recover 会返回 nil 并且没有其他任何效果,

背景:Go语言追求简洁优雅,所以,Go语言不支持传统的 try…catch…finally 这种异常,因为Go语言的设计者们认为,将异常与控制结构混在一起会很容易使得代码变得混乱。因为开发者很容易滥用异常,甚至一个小小的错误都抛出一个异常。在**Go语言中,使用多值返回来返回错误。不要用异常代替错误,更不要用来控制流程。**在极个别的情况下,才使用Go中引入的Exception处理:defer, panic, recover。

panic:
1、内建函数
2、假如函数F中书写了panic语句,会终止其后要执行的代码,在panic所在函数F内如果存在要执行的defer函数列表,按照defer的逆序执行
3、返回函数F的调用者G,在G中,调用函数F语句之后的代码不会执行,假如函数G中存在要执行的defer函数列表,按照defer的逆序执行,这里的defer 有点类似 try-catch-finally 中的 finally
4、直到goroutine整个退出,并报告错误

recover:
1、内建函数
2、用来控制一个goroutine的panicking行为,捕获panic,从而影响应用的行为
3、一般的调用建议
a). 在defer函数中,通过recever来终止一个gojroutine的panicking过程,从而恢复正常代码的执行
b). 可以获取通过panic传递的error

简单来讲:go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理。

func main() {
      fmt.Println("c")
   defer func() { // 必须要先声明defer,否则不能捕获到panic异常
      fmt.Println("d")
      if err := recover(); err != nil {
         fmt.Println(err) // 这里的err其实就是panic传入的内容
      }
      fmt.Println("e")
   }()
   f() //开始调用f
   fmt.Println("f") //这里开始下面代码不会再执行
}

func f() {
   fmt.Println("a")
   panic("异常信息")
   fmt.Println("b") //这里开始下面代码不会再执行
}
-------output-------
c
a
d
异常信息
e

注意:利用recover处理panic指令,defer必须在panic之前声明,否则当panic时,recover无法捕获到panic.

Go语言宕机(panic)——程序终止运行

Go语言的类型系统会在编译时捕获很多错误,但有些错误只能在运行时检查,如数组访问越界、空指针引用等,这些运行时错误会引起宕机。

一般而言,当宕机发生时,程序会中断运行,并立即执行在该 goroutine(可以先理解成线程)中被延迟的函数(defer 机制),随后,程序崩溃并输出日志信息,日志信息包括 panic value 和函数调用的堆栈跟踪信息,panic value 通常是某种错误信息。

因此 panic 一般用于严重错误,如程序内部的逻辑不一致。任何崩溃都表明了我们的代码中可能存在漏洞,所以对于大部分漏洞,我们应该使用Go语言提供的错误机制,而不是 panic。

手动触发宕机
Go语言可以在程序中手动触发宕机,让程序崩溃,这样开发者可以及时地发现错误,同时减少可能的损失。

Go语言程序在宕机时,会将堆栈和 goroutine 信息输出到控制台,所以宕机也可以方便地知晓发生错误的位置,那么我们要如何触发宕机呢?

package main
func main() {
    panic("crash")
}

六、参考

go安装与goland破解永久版
参考URL: https://www.cnblogs.com/zhangguosheng1121/p/11448194.html
go语言入门(1)
https://www.cnblogs.com/xdyixia/p/11847424.html
[推荐-写的比较全面]Go 语言设计与实现
参考URL: https://draveness.me/golang/

  • 5
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
Leaf/go 是一个基于 Golang 语言开发的游戏服务器框架,它提供了一整套高效、灵活的游戏开发解决方案。下面是对 Leaf/go 的详细分析: 1. 网络层 Leaf/go 提供了一个基于 TCP 协议的网络层,它使用了 epoll 和 kqueue 等高效的 I/O 多路复用技术,可以支持高并发的连接处理。同时,Leaf/go 还支持自定义协议和数据编解码方式,使得开发者可以根据自己的需求进行灵活的网络协议设计。 2. 框架结构 Leaf/go 的框架结构非常清晰,分为了四个模块:组件、节点、服务和模块。其中,组件是框架的基础设施,比如网络组件、日志组件、定时器组件等等;节点是逻辑处理的单元,比如游戏世界节点、战斗节点、聊天节点等等;服务是节点的集合,它负责协调节点之间的通信和数据同步;模块则是游戏逻辑的实现,比如登录模块、商城模块、任务模块等等。这种结构清晰、层次分明的设计思路,使得 Leaf/go 可以快速构建出复杂的游戏系统。 3. 代码质量 Leaf/go 的代码质量非常高,结构清晰,注释详细,命名规范。在代码实现上,Leaf/go 使用了一些 Golang 的高级特性,比如协程、管道等等,这些特性使得代码具有高效、可读性强的特点。同时,Leaf/go 还提供了完善的测试机制和文档,方便开发者进行代码测试和文档阅读。 4. 社区支持 Leaf/go 拥有一个活跃的社区,开发者可以在社区中寻求帮助、分享经验和交流思路。同时,社区中也有很多优秀的游戏服务器开发经验和案例可以供开发者借鉴和学习。 总之,Leaf/go 是一个非常优秀的游戏服务器框架,它具有高效、灵活、易用等特点,可以帮助开发者快速构建出高质量的游戏系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西京刀客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值