Go语言开发环境搭建【Win、Linux、Mac】
1 SDK下载
根据自己操作系统版本,下载安装即可,目录尽量选择全英文且没有空格和其他其他特殊字符。
2 环境变量配置[GOPATH、GOROOT]
2.1 Windows下
讲解:
2.2 linux下
uname -a
32位系统下载:gox.x.x.linux-386.tar.gz
64位系统下载:gox.x.x.linux–amd64.tar.gz
#进入对应目录并解压
cd /opt
tar -zxvf go1.9.2.linux-amd64.tar.gz
# 输入下面命令回车并输入密码后进入root权限【linux输入密码默认不显示,直接输入即可】
su root
# 刷新环境变量
source /etc/profile
# 或者注销后重新登录
3 检测
不论在windows还是linux下,直接打开终端,输入go version
即可:
4 开发简单go程序
4.1 hello.go程序入门
①文件目录结构
②开发工具:选择Goland或者VSCode等
//每个go文件都需要归属于一个包
package main
//下面用到了fmt的Println函数,因此需要导包
import "fmt"
func main(){
fmt.Println("hello,go")
}
运行:
//方式一:将hello.go编译为hello.exe文件,然后再直接运行hello.exe
go build hello.go
.hello.exe
//方式二:直接以脚本方式运行(底层还是编译过了)
go run hello.go
//将hello.go编译生成myhello.exe文件
go build -o myhello.exe hello.go
4.2 go的执行流程分析
5 Go语言开发的注意事项
①注意事项
②go的转义字符及注释
转义字符:
注释:
被注释的内容不会被go编译器所编译
- 行注释:
//
//fmt.Println("jack")
- 块注释:
/**/
/*
fmt.Println("tom")
fmt.Println("jucy)
*/
③编程风格及API地址
//花括号只有这一种写法,go开发者认为应当统一风格
func main(){
}
- API地址
6 go modules管理
6.1 概念
6.2 go mod 环境变量
注意:
6.3 go mod 命令
①开启go modules模式(建议go v1.11之后,都设置为on)
②设置proxy代理
//1. 初始化,生成.mod文件
go mod init
//2. 下载go.mod文件中指明的所有依赖
go mod download
//3. 整理现有依赖
go mod tidy
//4. 查看现有依赖结构
go mod graph
//5. 编辑go.mod文件
go mod edit
//6. 导出项目所有的依赖到vendor目录
go mod vendor
//7. 校验一个模块是否被篡改过
go mod verify
//8. 查看为什么需要依赖某模块
go mod why
6.3 其他命令
/*
-u :upgrade
如果不加这个 -u 标记,执行 go get 一个已有的代码包,会发现命令什么都不执行。
只有加了 -u 标记,命令会去执行 git pull 命令拉取最新的代码包的最新版本,
下载并安装。
*/
go get -u
6.4 go module 之前的做法[GO_PATH/src下放依赖]
- go是在1.11版本开始支持module的,因此在之前的做法是下载好依赖之后会将依赖下载到PATH的src下面,同时目录结构与go get的目录结构对应,比如:go get github.com/ying32/govcl,则依赖下载完成后会放在
%GO_PATH%/src
下创建%GO_PATH%/src/github.com/ying32/govcl
。- 同时在go之前的版本是没有GOPROXY的,因此通过 go get 来获取github上的资源会很慢,所以我们可以手动到github上下载源码,然后自己手动创建对应目录
- 在goland中如果是go module之前也需要关闭goland集成的go module(settings-go-go modules)
- 下载指定版本依赖,可以根据git clone 到对应git仓库,然后通过
git checkout
切换到指定版本
# eef842397966为go.mod文件中版本的最后一部分
git checkout eef842397966
6.5 go实现生产者消费者模型
package main
import "fmt"
/*
golang实现生产者消费者模型
*/
func main() {
ch := make(chan int, 20) //存放生产者生产好的数据
numProduc := 3 //生产者数量
numConsum := 5 //以5个消费者数量为例
donePr := make(chan bool, numProduc) //donePr的容量:生产者数量
doneCo := make(chan bool, numConsum) //doneCo的容量:消费者数量
//开启多个生产者
for i := 1; i <= numProduc; i++ {
go Producer(i, ch, donePr)
}
//开启多个消费者
for j := 1; j <= numConsum; j++ {
go Consumer(j, ch, doneCo)
}
for ii := 0; ii < numProduc; ii++ {
<-donePr //done通道内的数据全部写出,代表所有生产结束,ch通道可以关闭
}
close(ch)
for jj := 0; jj < numConsum; jj++ {
<-doneCo //把done通道内的数据写出,起到一个wait()的作用,等待子进程全部结束之后,主进程便可退出
}
}
//生产者模型
func Producer(id int, ch chan int, done chan bool) {
for i := 0; i <= 10; i++ {
ch <- i
fmt.Printf("%v号生产者,产生数据:%v n", id, i)
}
done <- true
//在开启多个goroutine进行生产时,不能再函数内部将通道关闭
//此处往done通道内写数据,目的是为了表示goroutine生产完毕
//当在主函数中能取出相应数量(numProduc)的元素时,即代表所有生产结束,通道可关闭
}
//消费者
func Consumer(id int, ch chan int, done chan bool) {
for v := range ch {
fmt.Printf("%v号消费者,消费数据:%v n", id, v)
}
done <- true
//函数主体结束时,往通道里写一个数据
//当开启多个goroutine时,每个协程结束都往通道写一个数据,相当于告诉外界,我消费结束了
//在主函数末尾,依次取出该通道内数据,当全部取出,就代表所有子goroutine结束,主程序也可以退出
//相当于起到同步等待的作用
}
7 bug合集
7.1 导包问题
①missing go.sum entry for module providing package <package_name>
go在使用第三方的时候导入包报错:missing go.sum entry for module providing package <package_name>
解决办法一:
# 执行下面命令,删除不需要的依赖包,下载新的依赖包,更新go.sum
go mod tidy
如果执行上面命令报错:go mod tidy: go.mod file indicates go 1.19, but maximum supported version is 1.17;
go build -mod = mod
②unrecognized import path “golang.org/x/sys/windows“问题
执行语句:go get github.com/StackExchange/wmi,报go get github.com/StackExchange/wmi
- 解决办法:
③ module declares its xxx but was required as xxx
7.2 文件问题
①双击exe文件闪退
可能是本地系统的go版本与IDE的go版本不兼容,比如:goland上是go1.19,本地windows上是go1.10,修改之后重新build即可
②写入文件报错:Access is denied.
file, err := os.Open(path)
defer file.Close()
_, err = file.WriteString(data)
7.3 代码问题
①序列化失败
type LocalIp struct {
Name string `json:"name"`
Ip string `json:"ip"`
}
②以相对路径打开文件失败
我在项目目录下创建子目录,然后通过子目录来运行代码报错:open file err= open …/…/test.zip: The system cannot find the file specified.
我们本地通过goland运行go代码的时候,go默认是会以当前项目的根目录去找文件,因此尽量使用绝对路径或者在相对路径的前面添加上自己的目录名
③协程无法获取正确的值
package main
import (
"fmt"
"sync"
)
var (
wg = new(sync.WaitGroup)
)
func main() {
keys := []string{"key1", "key2", "key3", "key4", "key5"}
for _, k := range keys {
wg.Add(1)
go func() {
printKey(k)
}()
}
wg.Wait()
}
func printKey(key string) {
defer wg.Done()
fmt.Println(key)
}
正确做法:
func main() {
keys := []string{"key1", "key2", "key3", "key4", "key5"}
var wg sync.WaitGroup
for _, k := range keys {
wg.Add(1)
go func(key string) {
defer wg.Done()
printKey(key)
}(k)
}
wg.Wait()
}
func printKey(key string) {
fmt.Println(key)
}
8 其他tips
8.1 将源文件编码为linux可执行文件
下面命令均在windows上执行
# 设置环境(仅对当前终端有效)
set GOARCH=amd64
set GOOS=linux
go build main.go
# 打包后会生成一个main程序,将此程序拷贝至linux服务器,两种方式启动:
1、在当前会话执行
./main
2、后台启动
setsid ./main
# 或则直接组合起来一行命令搞定
GOOS=linux GOARCH=amd64 go build main.go
# 编码为windows上可以执行文件,运行之后会直接生成.exe文件
go build main.go
9 go中Context用法
9.1 概念
- context可以用来在goroutine之间传递上下文信息,相同的context可以传递给运行在不同goroutine中的函数,上下文对于多个goroutine同时使用是安全的,context包定义了上下文类型,可以使用background、TODO创建一个上下文,在函数调用链之间传播context,也可以使用WithDeadline、WithTimeout、WithCancel 或 WithValue 创建的修改副本替换它,听起来有点绕,其实总结起就是一句话:context的作用就是在不同的goroutine之间同步请求特定的数据、取消信号以及处理请求的截止日期。
- 目前我们常用的一些库都是支持context的,例如gin、database/sql等库都是支持context的,这样更方便我们做并发控制了,只要在服务器入口创建一个context上下文,不断透传下去即可。
9.2 使用
上面的两种方式是创建根context,不具备任何功能,具体实践还是要依靠context包提供的With系列函数来进行派生:
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
基于一个父Context可以随意衍生,其实这就是一个Context树,树的每个节点都可以有任意多个子节点,节点层级可以有任意多个,每个子节点都依赖于其父节点,例如上图,我们可以基于Context.Background衍生出四个子context:ctx1.0-cancel、ctx2.0-deadline、ctx3.0-timeout、ctx4.0-withvalue,这四个子context还可以作为父context继续向下衍生,即使其中ctx1.0-cancel 节点取消了,也不影响其他三个父节点分支。
9.2.1 WithValue(携带数据:trace_id)
①概念及使用
我们日常在业务开发中都希望能有一个trace_id能串联所有的日志,这就需要我们打印日志时能够获取到这个trace_id,在python中我们可以用gevent.local来传递,在java中我们可以用ThreadLocal来传递,在Go语言中我们就可以使用Context来传递,通过使用WithValue来创建一个携带trace_id的context,然后不断透传下去,打印日志时输出即可,来看使用例子:
const (
KEY = "trace_id"
)
func NewRequestID() string {
return strings.Replace(uuid.New().String(), "-", "", -1)
}
func NewContextWithTraceID() context.Context {
ctx := context.WithValue(context.Background(), KEY,NewRequestID())
return ctx
}
func PrintLog(ctx context.Context, message string) {
fmt.Printf("%s|info|trace_id=%s|%s",time.Now().Format("2006-01-02 15:04:05") , GetContextValue(ctx, KEY), message)
}
func GetContextValue(ctx context.Context,k string) string{
v, ok := ctx.Value(k).(string)
if !ok{
return ""
}
return v
}
func ProcessEnter(ctx context.Context) {
PrintLog(ctx, "Golang梦工厂")
}
func main() {
ProcessEnter(NewContextWithTraceID())
}
结果:
2021-10-31 15:13:25|info|trace_id=7572e295351e478e91b1ba0fc37886c0|Golang梦工厂
Process finished with the exit code 0
我们基于context.Background创建一个携带trace_id的ctx,然后通过context树一起传递,从中派生的任何context都会获取此值,我们最后打印日志的时候就可以从ctx中取值输出到日志中。目前一些RPC框架都是支持了Context,所以trace_id的向下传递就更方便了。
②注意事项
- 不建议使用context值传递关键参数,关键参数应该显示的声明出来,不应该隐式处理,context中最好是携带签名、trace_id这类值。
- 因为携带value也是key、value的形式,为了避免context因多个包同时使用context而带来冲突,key建议采用内置类型。
- 上面的例子我们获取trace_id是直接从当前ctx获取的,实际我们也可以获取父context中的value,在获取键值对时,我们先从当前context中查找,没有找到会在从父context中查找该键对应的值直到在某个父context中返回 nil 或者查找到对应的值。
- context传递的数据中key、value都是interface类型,这种类型编译期无法确定类型,所以不是很安全,所以在类型断言时别忘了保证程序的健壮性。
9.2.2 WithTimeoutWithDeadline(超时控制)
通常健壮的程序都是要设置超时时间的,避免因为服务端长时间响应消耗资源,所以一些web框架或rpc框架都会采用withTimeout或者withDeadline来做超时控制,当一次请求到达我们设置的超时时间,就会及时取消,不在往下执行。withTimeout和withDeadline作用是一样的,就是传递的时间参数不同而已,他们都会通过传入的时间来自动取消Context,这里要注意的是他们都会返回一个cancelFunc方法,通过调用这个方法可以达到提前进行取消,不过在使用的过程还是建议在自动取消后也调用cancelFunc去停止定时减少不必要的资源浪费。
withTimeout、WithDeadline不同在于WithTimeout将持续时间作为参数输入而不是时间对象,这两个方法使用哪个都是一样的,看业务场景和个人习惯了,因为本质withTimout内部也是调用的WithDeadline。
func main() {
HttpHandler()
}
func NewContextWithTimeout() (context.Context,context.CancelFunc) {
return context.WithTimeout(context.Background(), 3 * time.Second)
}
func HttpHandler() {
ctx, cancel := NewContextWithTimeout()
defer cancel()
deal(ctx)
}
func deal(ctx context.Context) {
for i:=0; i< 10; i++ {
time.Sleep(1*time.Second)
select {
case <- ctx.Done():
fmt.Println(ctx.Err())
return
default:
fmt.Printf("deal time is %dn", i)
}
}
}
输出结果:
deal time is 0
deal time is 1
deal time is 2
context deadline exceeded
/*
deal time is 0
deal time is 1
context deadline exceeded
*/
func main() {
HttpHandler1()
}
func NewContextWithTimeout1() (context.Context,context.CancelFunc) {
return context.WithTimeout(context.Background(), 3 * time.Second)
}
func HttpHandler1() {
ctx, cancel := NewContextWithTimeout1()
defer cancel()
deal1(ctx, cancel)
}
func deal1(ctx context.Context, cancel context.CancelFunc) {
for i:=0; i< 10; i++ {
time.Sleep(1*time.Second)
select {
case <- ctx.Done():
fmt.Println(ctx.Err())
return
default:
fmt.Printf("deal time is %dn", i)
cancel()
}
}
}
结果:
deal time is 0
context canceled
使用起来还是比较容易的,既可以超时自动取消,又可以手动控制取消。这里大家要记的一个坑,就是我们往从请求入口透传的调用链路中的context是携带超时时间的,如果我们想在其中单独开一个goroutine去处理其他的事情并且不会随着请求结束后而被取消的话,那么传递的context要基于context.Background或者context.TODO重新衍生一个传递,否决就会和预期不符合了,可以看一下我之前的一篇踩坑文章:context使用不当引发的一个bug。
9.2.3 WithCancel取消控制
日常业务开发中我们往往为了完成一个复杂的需求会开多个gouroutine去做一些事情,这就导致我们会在一次请求中开了多个goroutine确无法控制他们,这时我们就可以使用withCancel来衍生一个context传递到不同的goroutine中,当我想让这些goroutine停止运行,就可以调用cancel来进行取消。
func main() {
ctx,cancel := context.WithCancel(context.Background())
go Speak(ctx)
time.Sleep(10*time.Second)
cancel()
time.Sleep(1*time.Second)
}
func Speak(ctx context.Context) {
for range time.Tick(time.Second){
select {
case <- ctx.Done():
fmt.Println("我要闭嘴了")
return
default:
fmt.Println("balabalabalabala")
}
}
}
运行结果:
balabalabalabala
....省略
balabalabalabala
我要闭嘴了
我们使用withCancel创建一个基于Background的ctx,然后启动一个讲话程序,每隔1s说一话,main函数在10s后执行cancel,那么speak检测到取消信号就会退出。
参考:
https://blog.csdn.net/Mr_XiMu/article/details/124671852
https://segmentfault.com/a/1190000040917752
原文地址:https://blog.csdn.net/weixin_45565886/article/details/130175889
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_47126.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!