前言:Go语言(或称Golang)是由Google开发的一种编程语言,具有高效、简洁和可靠性等特点。它被设计用于快速构建可靠的软件,支持并发、垃圾回收和内置的并行计算等特性。Go语言拥有丰富的标准库,同时也有很多第三方库可以供使用。它适用于开发各种类型的应用程序,包括网络服务器、分布式系统、云服务以及移动应用程序等。
一、Golang安装与配置
要安装和配置Golang,您可以按照以下步骤进行操作:
1.访问官方网站:前往Golang的官方网站(https://golang.org/),在主页上找到适合您操作系统的下载链接。
2.下载安装包:点击下载链接,选择与您操作系统相对应的安装包,并将其下载到本地计算机上。
3.安装Golang:找到您下载的安装包文件并运行。根据提示,按照默认设置进行安装即可。
4.配置环境变量:一旦安装完成,需要配置环境变量以便在命令行中使用Golang。打开终端或命令提示符窗口,在其中输入以下内容:
在Linux/macOS上:
export PATH=$PATH:/usr/local/go/bin
在Windows上:
setx PATH "%PATH%;C:\Go\bin"
注意:以上路径是默认路径,请根据实际情况修改。
5.验证安装成功:打开终端或命令提示符窗口,输入以下命令来验证是否成功安装和配置了Golang:
go version
6.如果显示了Golang的版本信息,则说明安装和配置成功。已经成功安装和配置了Golang,可以开始使用它来编写和运行Go语言程序了。
常用的配置项:
GO111MODULE
: 是否开启模块支持;on-开启,off-关闭,auto-自动GOPROXY
: go包代理地址配置;官方的国内访问有问题GOGCCFLAGS
: 环境信息GOGCCFLAGS的值则是Go语言在使用操作系统的默认C语言编译器对C语言代码进行编译时加入的参数。GOPATH
: go的工作空间目录;我们使用模块化管理后,下载的包会放在这个文件夹的pkg下面;
二、GO语言基础语法
GO语言基础语法包括以下内容:
- 包声明:每个Go程序都由包组成,通过
package
关键字进行声明。常用的包有fmt
、os
、io
等。 - 导入其他包:使用
import
关键字导入需要使用的其他包。 - 函数声明:使用
func
关键字定义函数,可以指定参数和返回值类型。 - 变量声明:使用关键字
var
声明变量,并指定变量类型。
控制流程语句:
- 条件判断语句:使用
if-else
或switch-case-default
- 循环语句:使用
for
,range
数据类型:
- 基本数据类型:int, float, bool, string
- 复合数据类型:数组(Array)、切片(Slice)、映射(Map)、结构体(Struct)
指针和引用类型:可以通过 &
获取变量的内存地址,通过 *
解引用指针获取对应的值。
方法和接口:Go支持面向对象编程,可以为自定义类型定义方法,并实现接口。
错误处理:Go推荐使用错误返回值来处理异常情况,通常将最后一个返回值设为error类型。
并发与协程:Go内置了并发编程模型goroutine和通道channel,方便编写高效的并发代码。
2.1Go标记
Go 程序可以由多个标记组成,可以是关键字,标识符,常量,字符串,符号。如以下 GO 语句由 6 个标记组成:
fmt.Println("Hello, World!")
2.2行分隔符
在 Go 程序中,一行代表一个语句结束。每个语句不需要像 C 家族中的其它语言一样以分号 ; 结尾,因为这些工作都将由 Go 编译器自动完成。
如果你打算将多个语句写在同一行,它们则必须使用 ; 人为区分,但在实际开发中我们并不鼓励这种做法。
以下为两个语句:
fmt.Println("Hello, World!") fmt.Println("某某教程:runoob.com")
2.3注释
注释不会被编译,每一个包应该有相关注释。
单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾。如:
// 单行注释 /* Author by 菜鸟教程 我是多行注释 */
2.4标识符
标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母(A~Z和a~z)数字(0~9)、下划线_组成的序列,但是第一个字符必须是字母或下划线而不能是数字。
以下是有效的标识符:
mahesh kumar abc move_name a_123 myname50 _temp j a23b9 retVal
2.5关键字
下面列举了 Go 代码中会使用到的 25 个关键字或保留字:
除了以上介绍的这些关键字,Go 语言还有 36 个预定义标识符:
程序一般由关键字、常量、变量、运算符、类型和函数组成。
程序中可能会使用到这些分隔符:括号 (),中括号 [] 和大括号 {}。
程序中可能会使用到这些标点符号:.、,、;、: 和 …。
2.6Go 语言的空格
Go 语言中变量的声明必须使用空格隔开,如:
var age int
在变量与运算符间加入空格,程序看起来更加美观,如:
fruit = apples + oranges
三、go特性
- 简洁易读:Go语言的设计目标是简洁易读,注重代码的可读性和可维护性。
- 并发支持:Go语言内置了协程(goroutine)和通道(channel),方便实现并发编程,可以高效地利用多核处理器。
- 垃圾回收:Go语言拥有自动垃圾回收机制,开发者无需手动管理内存分配和释放,减轻了程序员的负担。
- 快速编译:Go语言的编译速度非常快,可以在很短的时间内将代码编译成机器码,并且生成的可执行文件体积小巧。
- 静态类型和强类型:Go语言是静态类型和强类型语言,变量需要声明类型,并且不允许隐式类型转换,这样可以提高代码的安全性和可读性。
- 内置工具丰富:Go语言提供了丰富的标准库和工具集,包括网络、文件操作、测试、调试等方面,为开发者提供了便利。
- 跨平台支持:Go语言可以在各种主流操作系统上进行开发,并且能够方便地交叉编译生成不同平台下的可执行文件。
3.1垃圾回收
- a、内存自动回收,再也不需要开发人员管理内存 //开发代码中不能存在无引用的变量,不然代码出错
- b、开发人员专注业务实现,降低了心智负担
- c、只需要new分配内存,不需要释放
3.2天然并发
- a、从语言层面支持并发,非常简单
- b、goroute,轻量级线程,创建成千上万个goroute成为可能
- c、基于CSP(Communicating Sequential Process)模型实现(基于goroute、channel)
并发实例:
package main import( "time" "fmt" ) func test_goroute(a int) { fmt.Println(a) } func main() { for i := 0; i < 100; i++ { go test_goroute(i) } time.Sleep(time.Second) }
3.3channel(管道,进程间的通信)
- a、管道,类似unix/linux中的pipe
- b、多个goroute之间通过channel进行通信
- c、支持任何类型
func main() { pipe := make(chan int,3) //make 分配内存空间 pipe <- 1 //向管道里面扔数值 pipe <- 2 // len(pipe) 管道的长度 t1 =<- pipe //取管道里面的数据 }
管道的传值:main/main.go
package main import( "go_dev/day1/goroute_example/goroute" "fmt" ) func main() { var pipe chan int pipe = make(chan int, 1) go goroute.Add(100, 300, pipe) sum := <- pipe fmt.Println("sum=", sum) }
goroute/add.go
package goroute func Add(a int, b int, c chan int) { sum := a +b c <- sum }
多返回值:一个函数返回多个值main/main.go
package main import( "go_dev/day1/package_example/calc" //调用其他包,路径根据GOPANTH不用写src,直接从src目录下开始 "fmt" ) func main() { sum,_ := calc.Add(100, 300) //接收返回值得一个,_没有值,打印报错 fmt.Println("sum=",sum) }
calc/Add.go
package calc func Add(a int, b int) (int,int) { return a + b,a - b }
四、Go并发
Go语言是一种支持并发编程的编程语言。它内置了轻量级的协程(goroutine)和通信机制(channel),可以方便地进行并发编程。
在Go语言中,使用关键字"go"可以创建一个新的协程。协程是一种轻量级的线程,可以同时执行多个任务,而不需要显式地管理线程生命周期。通过协程,我们可以并发地执行多个函数或方法。
另外,Go语言提供了通信机制来实现不同协程之间的数据传递和同步操作。通信机制主要是通过channel来实现的。通过channel,一个协程可以向另一个协程发送数据,并且会阻塞等待对应的接收操作;反之亦然。
这种基于协程和通信的并发模型使得在Go语言中编写高效、简洁、安全的并发程序变得相对容易。同时,Go语言还提供了丰富的标准库以及第三方库来支持各种并发相关的操作和模式,如锁、条件变量、原子操作等。
4.1调整并发的运行性能(DOMAXPROCS)
传统逻辑中,开发者需要维护线程池中线程与CPU核心数量的对应关系。同样的,Go地中也可以通过runtime.GOMAXPROCS()函数做到,格式为:
runtime.GOMAXPROC(逻辑cpu数量)
几种数值:
- <1:不修改任何数值。
- =1:单核心执行。
- '>1':多核并发执行
runtime.Num CPU()查询CPU数量,并使用runtime.GOMAXPROCS()函数进行设置,例如:
runtime.GOMAXPROC(runtime.NumCPU())
4.2并发和并行
并发(concurrency):把任务在不同的时间点交给处理器进行处理。在同一时间点,任务并不会同时运行。
并行(parallelism):把每一个任务分配给每一个处理器独立完成。在同一时间点,任务一定是同时运行。
GO在GOMAXPROCS数量与任务数量相等时,可以做到并行执行,但一般情况下都是并发执行。
goroutine属于抢占式任务处理,已经和现有的多线程和多进程任务处理非常类似。应用程序对CPU的控制最终还需要由操作系统来管理,操作系统如果发现一个应用程序长时间大量地占用CPU,那么用户有权终止这个任务。
4.3通道(channel)---在多个goruntine间通信的管道
Go语言提倡使用通信的方法代替共享内存,这里通信的方法就是使用通道(channel)
特性
任何时候,同时只能有一个goroutine访问通道进行发送和获取数据。goroutine间通过通道就可以通信。
通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。
使用通道接收数据
通道接收同样使用“<-”操作符,通道接收有如下特性:
- 通道的收发操作在不同的两个goroutine间进行。由于通道的数据在没有接收方处理时,数据发送方会持续阻塞,因此通道的接收必定在另外一个goroutine中进行。
- 接收将持续阻塞直到发送方发送数据。如果接收方接收时,通道中没有发送方发送数据,接收方也会发生阻塞,直到发送方发送数据为止。
- 每次接收一个元素。
- 使用非阻塞方式从通道接收数据时,语句不会发生阻塞
非阻塞的通道接收方法可能造成高的CPU占用,因此使用非常少。如果需要实现接收超时检测,可以配合select和计时器channel进行
循环接收
通道的数据接收可以借用for range语句进行多个元素的接收操作
for data := range ch { }
单向通道
Go的通道可以在声明时约束其操作方向,如只发送或是只接收。这种被约束方向的通道被称做单向通道。
只能发送的通道类型为chan<-,只能接收的通道类型为<-chan,格式如下:
var 通道实例 chan<- 元素类型 // 只能发送通道 var 通道实例 <-chan元素类型 // 只能接收通道
但是,一个不能填充数据(发送)只能读取的通道是毫无意义的。
time包中的单向通道
timer := time.NewTimer(time.Second)
timer的Timer类型定义如下:
type Timer struct { C <-chan Time r runtime Timer }
C通道的类型就是一种只能接收的单向通道。如果此处不进行通道方向约束,一旦外部向通道发送数据,将会造成其他使用到计时器的地方逻辑产生混乱
带缓冲的通道
在无缓冲通道的基础上,为通道增加一个有限大小的存储空间形成带缓冲通道
无缓冲通道保证收发过程同步。无缓冲收发过程类似于快递员给你电话让你下楼取快递,整个递交快递的过程是同步发生的,你和快递员不见不散。但这样做快递员就必须等待所有人下楼完成操作后才能完成所有投递工作。如果快递员将快递放入快递柜中,并通知用户来取,快递员和用户就成了异步收发过程,效率可以有明显的提升。带缓冲的通道就是这样的一个“快递柜”。
- 创建带缓冲通道
通道实例 := make(chan通道类型,缓冲大小)
通道类型:和无缓冲通道用法一致,影响通道发送和接收的数据类型。
- 通道类型:和无缓冲通道用法一致,影响通道发送和接收的数据类型。
- 带缓冲通道为空时,尝试接收数据时发生阻塞。
通道的多路复用
多路复用通常表示在一个信道上传输多路信号或数据流的过程和技术
网线、光纤也都是基于多路复用模式来设计的,网线、光纤不仅可支持同时收发数据,还支持多个人同时收发数据。
提供了select关键字,可以同时响应多个通道的操作。select的每个case都会对应一个通道的收发过程.当收发完成时,就会触发case中响应的语句。多个操作在每次select中挑选一个进行响应.
select{ case 操作1: 响应操作1 case 操作2: 响应操作2 ... default: }
模拟远程过程调用(RPC)
服务器开发中会使用RPC(Remote Procedure Call,远程过程调用)简化进程间通信的过程。RPC能有效地封装通信过程,让远程的数据收发通信过程看起来就像本地的函数调用一样。
控制并发数
- 令牌桶的思路:使用chan的缓冲数来控制每次处理任务的最大并发数
func main() { ch := make(chan int,100) for i := 0; i < 1000; i++ { ch <- i go func(){ // deal time.Sleep(time.Second) <- ch }() } }
并发的安全退出
有时候需要通知Gorutine停止运行,特别是当它在错误的方向上。Go语言并没有提供一个直接终止Goroutine的方法,因为这样会导致Goroutine之间的共享变量处在未定义的状态上
- 借助select及sync.WaitGroup控制
当每个Goroutine收到退出指令退出时一般会进行一定的清理工作,但是退出的清理工作并不能保证被完成,因为main线程并没有等待各个工作Goroutine退出工作完成的机制。结合sync.WaitGroup来改进。
func main() { cancel := make(chan bool) var wg sync.WaitGroup for i :=0; i < 10; i++ { wg.Add(1) go worker(&wg, cancel) } time.Sleep(time.Second) close(cancel) wg.Wait() } func worker(wg *sync.WaitGroup, cancel chan bool) { defer wg.Done() for { select { deafult: cancel <- cancel: return } } }
- context包
标准库增加了一个context包,用来简化对于处理单个请求的多个Goroutine之间与请求域的数据、超时和退出等操作
func main() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) var wg sync.WaitGroup for i :=0; i < 10; i++ { wg.Add(1) go worker(ctx,&wg) } time.Sleep(time.Second) cancel() wg.Wait() } func worker(ctx context,Context, wg *sync.WaitGroup) error { defer wg.Done() for { select { deafult: cancel <- ctx.Done(): return ctx.Err() } } }
当并发体超时或main主动停止工作者Goroutine时,每个工作者都可以安全退出。
当main()函数完成工作前,通过调用cancel()来通知后台Goroutine退出,这样就避免了Goroutine的泄漏。
五、项目实战
5.1项目一:短信发送
- 公有云服务接入基本套路
- 短信签名与短信模板
- 短信应用创建及设置
- 短信发送demo实现
- 短信发送逻辑封装
- 短信模板注册
- 短信发送接口实现
- redis客户端初始化
- 短信验证码接口实
5.2项目二:邮件发送
- 邮件推送前置条件
- ses邮件推动demo
- ses邮件发送逻辑封装
- ses邮件模板注册
- ses邮件发送接口实现
- smtp邮件发送demo
- smtp发送邮件逻辑封装
- smtp邮件发送接口实现
- smtp发送邮件接口调试
5.3项目三:人脸识别
- 人机验证简介
- 验证码控制台配置及接入流程
- 验证码demo实现
- 验证码服务逻辑封装
- 验证码票据校验接口实现
5.4项目四:云点播/云直播项目
- 对象存储相关概览介绍
- 静态网站托管
- 图片压缩与图片样式
- 数据直传签名逻辑封装
- web数据直传实现
- 上传图片时压缩图片文件
PS:项目提供源码