这是一篇方向一的学习笔记,主要内容是对第六期青训营直播内容的总结,包括部分知识点的梳理,此外,还有本人对于本次的学习过程的一些感悟,希望对和我类似的入门同学们有一些帮助。
参加的第六次青训营的直播课程内容十分丰富,对于我这种入门小白来说,知识内容需要一段时间的自我消化才可以学会,虽然有一些语言学习的基础,但是因为语法知识不一样,所以在编写的时候,常会发生写错的情况,所以建议大家,可以把go语言的语法规则,和自己熟悉语言的语法规则,做一个整理笔记,主要记录一些区别,经常翻看有助于记忆;此外,go语言还具有一些更加独特的优势,建议也一并记录在笔记中。
知识梳理:
一、并发、并行的区别
并发和并发的主要区别:
并发使用一个核进行运算;并行同时有多个核进行运算;
Go语言可以充分发挥多核优势,实现高效的运行。
二、程序、进程、线程、协程的区别
以下内容是我对这些类似概念的一些分析:
程序是指示计算机或其他装置执行动作或做出判断的指令。
进程是系统进行资源分配和调度的基本单位,是操作系统结构的基础,是系统分配资源和调度的基本单位。
主要区分记忆线程、协程的概念:
线程****内核态,线程可以并发的跑多个协程,栈MB级别。
协程是用户态,轻量级线程,栈KB级别。
Go语言一次可以创建上万个协程,也是该语言可以实现高并发的原因。
三、协程间的通信
Go提倡通过通信来共享内存,G也保存着通过共享内存实现通信的机制。
通过通信来共享内存需要使用的是通道;
通过共享内存实现通信,需要使用互斥量进行加锁,可能在一定程度上影响程序的性能。
四、Channel的一些操作
4.1 通过make关键字创建,代码使用方法如下
make (chan 元素类型,[缓冲区大小])
分为有缓冲,无缓冲两种方式,以下将对两种方式进行具体展示:
有缓冲通道:
make(chan int ,2)
无缓冲通道:
make (chan int)
五、WaitGroup实现同步
Add(delta int ) 表示计数器加delta
Done() 表示计数器-1
Wait() 表示会一直阻塞直到计数器为0停止
六、依赖管理
在开发项目时,利用他人已经封装好的开发组件或工具提升研发效率。
实现简单函数时只依赖原生的sdk即可;
在实际的开发过程中,很多的依赖需要通过sdk的方式引入,因此对于依赖包的管理很重要。
6.1 Go的依赖管理阶段
有以下三个管理阶段:
GOPATH
Go Vendor
Go Moudle(最常用)
根据以下两点来决定选择哪种方式进行管理:
1.不同环境所依赖的版本
2.控制依赖库的版本
6.1.1 GOPATH
该目录下有三个关键点:
1. bin:编译后的二进制文件存于此处;
2. pkg:编译的中间产物,加速编译;
3. src:项目源码,项目工程存在src中。
GOPATH的弊端:无法实现package的多版本控制
6.1.2 Go Vendor
在该目录下存放副本,寻找依赖时先从vendor开始找,如果没有再寻找GOPATH。
Go Vendor:每个项目都引入一份依赖的副本,可以解决多个项目需要同一个package依赖冲突问题
Go Vendor弊端:依赖项目源码,无法控制依赖的版本,更新项目时有可能出现依赖冲突的问题,最终导致编译错误
6.1.3 Go Moudle
Go Moudle是依赖管理系统,解决了以前依赖管理系统存在的无法依赖同一个库的不同版本的问题。
通过go.mod 文件管理依赖包版本;
通过
go get
或者
go mod
指令工具管理依赖包
七、Go语言的特点:
1.是高性能、高并发的一门语言,不需要找经过高度性能优化的第三方库来开发应用;
2.语法简单,如:去掉了表达式的括号;循环只有for循环一种;
3.有很丰富的、质量有保障的标准库,大部分时候不需要使用第三方库就可以实现开发;
4.有丰富的工具链:如编译、代码格式化、代码补充提示;
5.静态编译;
6.快速编译;
7.跨平台:可以在linux、windows、MaxOs上运行,也可以开发安卓、ios软件,还可以在路由器、树莓派上运行;
8.带垃圾回收的语言,无需考虑内存的分配释放。
八、配置开发环境
1.浏览器输入 go.dev/ 进入官网下载;
2.可以使用VS,安装GO插件即可,也可以使用Goland,学生在校期间内可以申请免费的教育许可证;
九、基础语法
9.1 Hello world展示
package main
import (
"fmt"
)
func main() {
fmt.Println("hello world")
}
若要运行程序,使用以下代码
go run hello world.go
若要编译二进制的代码,需要使用go build实现编译,编译后用./hello world即可运行
9.2 go的变量类型
是强类型语言,每个变量都有自己的变量类型,包括bool ,float,int,字符串;
字符串是内置类型,可以直接使用+ =实现一些操作,如:”=“表示比较两个字符串,“+”表示 字符串的直接拼接操作 ;
9.3 变量声明
有两种方式:
1. var name = value
使用这种声明方式,go语言可以自动的推导出变量的类型;需要显示的声明变量类型也可写出来
2.变量名:=值
以下的代码将使用以上两种方式实现变量的声明:
package main
import (
"fmt"
"math"
)
func main() {
var a = "initial"
var b, c int = 1, 2
var d = true
var e float64
f := float32(e)
g := a + "foo"
fmt.Println(a, b ,c ,d,e, f)
fmt.Println(g)
}
9.4 常量
把9.3 变量中的var关键词改成const即可
要注意的是:常量没有确定的类型,会根据上下文自动确定常量的类型
package main
import (
"fmt"
"math"
)
func main() {
const s string = "constant"
const h = 500000000000
const i = 3e20 / h
fmt.Println(s, h ,i ,math.Sin(h), math.Sin(i))
}
9.5 if-else
与C的使用语法类似,仅有以下两个不同点:
1.if后不需要写括号,若写了括号,在保存时编辑器也会将其自动去掉
2.if 后一定要直接加大括号
if 7%2 == 0 {
fmt.Println("7 is even")
}
package main
import "fmt"
func main() {
if 7%2 == 0 {
fmt.Println("7 is even")
}else {
fmt.Println("7 is odd ")
}
if 8%4 == 0 {
fmt.Println("8 is divisible by 4")
}
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num , "has multiple disgits")
}
}
9.6 for循环
go语言中只有for循环一种;
当for循环里什么都不写时,就表示这是一个死循环;
同c一样,可以使用continue继续循环,也可以用Break跳出循环
十、程序优化
通过以上的语法学习,我们已经可以实现简单的go程序了;但是仅仅实现编写在实际的开发中,是一定不够的,我们需要对已经实现的程序进一步的优化,优化程序的性能,下面将详细阐述对程序进行优化的三个步骤
10.1 性能分析、瓶颈定位
首先进行性能分析,分析其瓶颈所在,在此基础上,才可以开展优化操作;
可以使用Go内置的pprof和trace工具,该工具通主要用于分析CPU和内存的使用情况,使我们可以快速找到程序的性能瓶颈,代码示例如下。
package main
import (
"fmt"
"log"
"net/http"
_ "net/http/pprof"
"time"
)
func main() {
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
for i := 0; i < 100000; i++ {
processData()
}
}
func processData() {
time.Sleep(10 * time.Millisecond)
}
10.2 瓶颈优化
常采用的方法有以下几种:
10.2.1 并发、并行
在本文第一部分的只是汇总中,我们得知Go语言支持并发和并行,充分的利用多核CPU资源,可以有效的提高程序的处理能力,代码展示如下:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
numWorkers := 4
numTasks := 100000
var wg sync.WaitGroup
wg.Add(numWorkers)
taskQueue := make(chan int, numTasks)
for i := 0; i < numWorkers; i++ {
go worker(&wg, taskQueue)
}
for i := 0; i < numTasks; i++ {
taskQueue <- i
}
close(taskQueue)
wg.Wait()
}
func worker(wg *sync.WaitGroup, tasks <-chan int) {
defer wg.Done()
for task := range tasks {
processTask(task)
}
}
func processTask(task int) {
time.Sleep(10 * time.Millisecond)
fmt.Println("Processed task:", task)
}
10.2.2 内存的分配和回收
要尽量避免频繁的内存分配和回收,以下的代码示例中,使用了runtime.GC()手动触发垃圾回收机制,以减少GC的负担,也是提高程序性能的一种方法。
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
numIterations := 100000
runtime.GC()
start := time.Now()
for i := 0; i < numIterations; i++ {
processData()
}
elapsed := time.Since(start)
fmt.Printf("Time taken: %s\n", elapsed)
}
func processData() {
time.Sleep(10 * time.Millisecond)
}
10.2.3 减少锁竞争
锁竞争可能导致性能下降,可以使用更细粒度的锁,或者使用无锁数据结构代替有锁的数据结构,从根源减少了锁竞争的出现,以此实现性能优化。
10.3 测试、比较
我们需要测试使用的优化方法是否真实的起了作用,可使用测试工具testing进行比较,代码展示如下。
package main
import (
"testing"
"time"
)
func BenchmarkProcessData(b *testing.B) {
for i := 0; i < b.N; i++ {
processData()
}
}
总结:以上的内容总结是go语言较为基础的语法知识,在此进行了一些整理,有书写不正确的地方,还请大家指出,谢谢!