go基础
go 文档
Go语言的并发是基于 goroutine 的,goroutine 类似于线程,但并非线程。可以将 goroutine 理解为一种虚拟线程。Go 语言运行时会参与调度 goroutine,并将 goroutine 合理地分配到每个 CPU 中,最大限度地使用CPU性能。开启一个goroutine的消耗非常小(大约2KB的内存),你可以轻松创建数百万个goroutine。
go语言特性
高效的性能
简洁的语法
广泛验证的工程效率
极致的部署体验
极低的服务端资源成本
go常用命令
go env
go env -w GOPROXY=https://goproxy.cn,direct
go env -w GO111MODULE=on
再没有 GO111MODULE时, go编译程序的查找依赖的顺序 go path > goroot , 有了GO111MODULE后,会读取当前项目的go.mod.文件, 在go.mod文件中会记录有哪些依赖
goroutine 协程的特点:
1.goroutine
具有可增长的分段堆栈。这意味着它们只在需要时才会使用更多内存。
2.goroutine
的启动时间比线程快。
3.goroutine
原生支持利用channel安全地进行通信。
4.goroutine
共享数据结构时无需使用互斥锁。
代码风格统一 一套格式化工具——go fmt
天生支持并发,完美契合当下高并发的互联网生态
go下载地址
官网地址:https://golang.google.cn/dl/
中文地址:https://studygolang.com/dl
1.18
goland 下载地址
https://www.jetbrains.com/go/download/#section=mac
帮助文档
https://www.jetbrains.com/help/go/2022.2/getting-started.html
配置goproxy
设置
go env -w GOPROXY=https://goproxy.cn,direct
com + , -> go ->go Modules ->environment:
GOPROXY=https://goproxy.cn,direct
go教程
https://golang.google.cn/doc/
https://topgoer.com/%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83/%E7%AC%AC%E4%B8%80%E4%B8%AAgo%E7%A8%8B%E5%BA%8F.html
go 安装
建立Go的工作空间(workspace,也就是GOPATH环境变量指向的目录)
GO代码必须在工作空间内。工作空间是一个目录,其中包含三个子目录:
src ---- 里面每一个子目录,就是一个包。包内是Go的源码文件
pkg ---- 编译后生成的,包的目标文件
bin ---- 生成的可执行文件
添加PATH环境
变量and设置GOPATH环境变量
vi /etc/profile
export GOROOT=/usr/local/go ##Golang安装目录
export PATH=$GOROOT/bin:$PATH
export GOPATH=/home/go ##Golang项目目录
go
go env
gopath
:GOPATH是一个环境变量,用来表明你写的go项目的存放路径
GOPATH路径最好只设置一个,所有的项目代码都放到GOPATH的src目录下
https://topgoer.com/%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83/%E9%85%8D%E7%BD%AEgopath.html
在进行Go语言开发的时候,我们的代码总是会保存在GOPATH/src目录下。在工程经过go build、go install或go get等指令后,会将下载的第三方包源代码文件放在$GOPATH/src目录下, 产生的二进制可执行文件放在 $GOPATH/bin目录下,生成的中间缓存文件会被保存在 $GOPATH/pkg 下
安装
go get gocv.io/x/gocv
第一个go
package main // 声明 main 包,表明当前是一个可执行程序
import "fmt" // 导入内置 fmt
func main(){ // main函数,是程序执行的入口
fmt.Println("Hello World!") // 在终端打印 Hello World!
}
go语法
声明
strName string
变量名 类型
函数
函数名大小写 表示包是否导出
函数多个形参 ... 三个点
func Test(a ...interface{}) (n int, err error)
任意类型 ...interface{}
构造函数
func newFunc() 不需要被导出
func NewFunc() 需要被导出
映射
map[string]string
结构体
type hao struct {
x,y float64
z int
}
结构体定义方法
func (h hao) demo() float64 {
return h.x+1.0
}
结构体初始化:
area := hao{1.0,2.0,5}
定义复合结构体: 可以分割独立可复用的结构当中
type hao struct {
hao1 hao1
hao2 hao2
hao3 //自动转发,不指定变量名, 会自动生成同名字段
}
h1 := hao1{}
h2 := hao2{}
h = hao{hao1:h1,hao2:h2, hao3:hao3{}}
h.hao3.value3
h.value3 //是相同字段
接口
类型必须满足一组方法的声明
type hao interface {
demo() string
}
type hao1 int
func (h1 hao1) demo() string{
return strings.Repeat("oyes",int(h1))
}
hao = hao1(3)
fmt.Println(hoa.talk())
指针
指针存储的内存地址
&调用传参内存地址 *解引用函数定义具体值
空指针解引用
hao := 666
fmt.Println(&hao) //变量的内存地址 0x1010c101
解引用
hao1 = &hao
// go语言不允许类似于c语言的hao1++这样的内存操作
fmt.Println(*hao1) //解引用
hao1 = "oyes"
var hao2 *string
hao2 := &hao1
fmt.Println(*hao2)
// *星号放在类型前面表示声明指针类型,*星号放在变量前面表示解引用该变量指向的值
解引用改变其值
*hao2 = "hao oyes"
hao3 := *hao2 //解引用,是拷贝的数据副本
// 内部指针:指向结构内部字段的指针 地址操作符&
type hao1 struct {
hao2
}
&hao1.hao2
func haof(h *hao2) string {}
指针没有赋值默认 为nil
空指针处理
func hoaf(h *hao) {
if h == nil {
return
}
h.page++
}
异常
demo,err := hao()
// 异常不等于空,有报错
if err != nil {
fmt.Println(err)
os.Exit(1)
}
for _,str := range demo{
}
func hao() []string,error{
return err
defer f.Close() //函数返回前执行
}
优雅的错误处理
type safeWriter struct {
w io.Writer
err Error
}
func (sw *safeWriter) writeln(s string){
if sw.err != nil {
return
}
_,sw.err = fmt.Fprintln(sw.w,s)
}
类型断言
err := g()
if err != nil {
if errs,ok :=err.(SudokuError); ok {
//具体的逻辑
}
os.Exit(1)
}
Go语言的错误值机制促使开发者考虑错误,而不是处理异常默认将其忽略。而且不需要用到特殊关键字,简单而灵活
recover() 被延迟的方法使用
defer func(){
if e:=recover(); e!=nil {
fmt.Pringln(e)
}
}()
panic("o no !!!")
并发
通过 goroutine并发执行,并通过channel实现多个goroutine之间的通信和协同
go hao() //goroutine
c := make(chan int) //channel 管道
c <- 99
r := c
func main(){
c := make(chan int)
for i := 0; i < 5; i++{
go sleepyGoher(i,c)
}
for i :=0; i<5; i++{
goherID := <-c
fmt.Pringln("goherId:",goherId)
}
}
func sleepyGoher(id int, c chan in ){
time.Sleep(3*time.Second)
fmt.Println("...",id,"snore ...")
c<-id
}
当 多通道,或等待超时时自动中断 time.After
timeout := time.After(2*time.Second)
for i:=0;i<5;i++{
select {
case gopherId:=<=c:
fmt.Println(gopherId)
case <-timeout: //等待耗尽
fmt.Println("time out")
return
}
}
阻塞和死锁
阻塞:goroutine在等待通道的发送或者接收,其被阻塞了
死锁: 一个或多个goroutine因某些永远不会发生的事情而被阻塞时,称这种情况为死锁, 死锁并不消耗资源
func main(){
c := make(chan int)
<- c
}
装配流水线
func source( down chan string){
for _,v :=range []string{"a","b","c"}
{
down <-v
}
down <- ""
close(donw) // 用close 代替空字符表示通道关闭
}
func filter(up,down chan string){
for {
item := <- up
v,ok := <- up // 获得状态 ok表示通道是否被关闭
if !ok {
close(down)
return
}
if item == ""{
down <- ""
return
}
if !strings.Contains(item,"bad"){
down <-item
}
}
}
优化后:
func filter(up,down chan string){
for item:= range up {
if !string.Contains(item,"bad"){
down<-item
}
}
close(down)
}
func printG(up chan string){
for{
v := up
if v != ""{
return
}
fmt.Println(v)
}
}
func main(){
up := make(chan string)
down := make(chan string)
go source(up)
go fileter(up,down)
printGopher(down)
}
并发状态 互斥锁
使用共享值为 竞态条件
互斥锁,组织多个goroutine操作同一值
死锁 :拥有互斥锁的goroutine,所动同一互斥锁,就会发生死锁
为了保证互斥锁的安全:
1、尽可能简化互斥锁的保护代码
2、对一份共享状态只使用一个互斥锁
var mu sync.Mutex //声明互斥锁
mu.Lock() //上锁
mu.Unlock() //解锁
defer mu.Unlock() //确保退出前一定解锁
死锁
go高级
并发
进程和线程
A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。
B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。
C.一个进程可以创建和撤销多个线程;同一个进程中的多个线程之间可以并发执行
并发和并行
A. 多线程程序在一个核的cpu上运行,就是并发。
B. 多线程程序在多个核的cpu上运行,就是并行。
协程和线程
协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的。
线程:一个线程上可以跑多个协程,协程是轻量级的线程。
goroutine 只是由官方实现的超级"线程池"
每个实例4~5KB的栈内存占用和由于实现机制而大幅减少的创建和销毁开销是go高并发的根本原因。
并发主要由切换时间片来实现"同时"运行,并行则是直接利用多核实现多线程的运行,go可以设置使用核数,以发挥多核计算机的能力。
goroutine 奉行通过通信来共享内存,而不是共享内存来通信。
goroutine
https://topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html
goroutine原理
要实现并发编程的时候,我们通常需要自己维护一个线程池,并且需要自己去包装一个又一个的任务,同时需要自己去调度线程执行任务并维护上下文切换,这一切通常会耗费程序员大量的心智。那么能不能有一种机制,程序员只需要定义很多个任务,让系统去帮助我们把这些任务分配到CPU上实现并发执行呢?
Go语言中的goroutine就是这样一种机制,goroutine的概念类似于线程,但 goroutine是由Go的运行时(runtime)调度和管理的。Go程序会智能地将 goroutine 中的任务合理地分配给每个CPU。Go语言之所以被称为现代化的编程语言,就是因为它在语言层面已经内置了调度和上下文切换的机制。
在Go语言编程中你不需要去自己写进程、线程、协程,你的技能包里只有一个技能–goroutine,当你需要让某个任务并发执行的时候,你只需要把这个任务包装成一个函数,开启一个goroutine去执行这个函数就可以了,就是这么简单粗暴。
GPM
是Go语言运行时(runtime)层面的实现,是go语言自己实现的一套调度系统。区别于操作系统调度OS线程
1.G很好理解,就是个goroutine的,里面除了存放本goroutine信息外 还有与所在P的绑定等信息。
2.P管理着一组goroutine队列,P里面会存储当前goroutine运行的上下文环境(函数指针,堆栈地址及地址边界),P会对自己管理的goroutine队列做一些调度(比如把占用CPU时间较长的goroutine暂停、运行后续的goroutine等等)当自己的队列消费完了就去全局队列里取,如果全局队列里也消费完了会去其他P的队列里抢任务。
3.M(machine)是Go运行时(runtime)对操作系统内核线程的虚拟, M与内核线程一般是一一映射的关系, 一个groutine最终是要放到M上执行的;
P与M一般也是一一对应的。他们关系是: P管理着一组G挂载在M上运行。当一个G长久阻塞在一个M上时,runtime会新建一个M,阻塞G所在的P会把其他的G 挂载在新建的M上。当旧的G阻塞完成或者认为其已经死掉时 回收旧的M。
P的个数是通过runtime.GOMAXPROCS设定(最大256),Go1.5版本之后默认为物理线程数。 在并发量大的时候会增加一些P和M,但不会太多,切换太频繁的话得不偿失。
单从线程调度讲,Go语言相比起其他语言的优势在于OS线程是由OS内核来调度的,goroutine则是由Go运行时(runtime)自己的调度器调度的,这个调度器使用一个称为m:n调度的技术(复用/调度m个goroutine到n个OS线程)。 其一大特点是goroutine的调度是在用户态下完成的, 不涉及内核态与用户态之间的频繁切换,包括内存的分配与释放,都是在用户态维护着一块大的内存池, 不直接调用系统的malloc函数(除非内存池需要改变),成本比调度OS线程低很多。 另一方面充分利用了多核的硬件资源,近似的把若干goroutine均分在物理线程上, 再加上本身goroutine的超轻量,以上种种保证了go调度方面的性能。
goroutine竞争状态
https://blog.csdn.net/qq_27449067/article/details/103882823
goroutine在没有相互同步状态的情况下同时访问某个资源,并且同时对这个资源进行读写的时候,对于这个资源就处于相互竞争状态(race candition)
var number int
var wait sync.WaitGroup
func main() {
wait.Add(2)
go updateNumber(20000)//加20000
go updateNumber(30000)//加30000
wait.Wait()
fmt.Println(number)
}
func updateNumber(addNumber int) {
for i:=0;i<addNumber ;i {
number
}
wait.Done()
}
原子函数
atomic能够以很底层的加锁机制来同步访问整型变量和指针,我们可以使用原子函数来处理竞争问题
var number int32
var wait sync.WaitGroup
func main() {
wait.Add(2)
go updateNumber(20000)
go updateNumber(30000)
wait.Wait()
fmt.Println(number)
}
func updateNumber(addNumber int) {
defer wait.Done()
for i:=0;i<addNumber ;i {
atomic.AddInt32(&number,1)
}
}
这个函数会同步整型值的加法,方法是强制同一时刻只能有一个goroutine 运行并完成这个加法操作
互斥锁 mutex
另一种方式是创建一个互斥锁来锁住一个区域,来保证同一个资源不会被同时修改或者使用。保证当前只有一个goroutine在执行当前区域的代码。
var (
number int32
wait sync.WaitGroup
mutex sync.Mutex
)
func main() {
wait.Add(2)
go updateNumber(20000)
go updateNumber(30000)
wait.Wait()
fmt.Println(number)
}
func updateNumber(addNumber int) {
defer wait.Done()
for i:=0;i<addNumber ;i {
mutex.Lock() // 加锁
number ;
mutex.Unlock() //释放锁
}
}
在Number改变的前后对当前区域加锁,最后也能得到我们的目的,但是这样的会话,每次在number变更的时候,都会创建锁与释放锁,会对性能产生很大的影响。其实我们可以在for循环区域来加锁
后面这种形式的效率明显是要比第一种高很多的。所以我们程序有使用互斥锁的话,需要考虑加锁的粒度问题
虽然上面上面两种方式也可以解决竞争问题,但是在go中有一种更好的方式来解决这个问题,那就是goroutine的好兄弟channel
go汇总
八股文
https://www.topgoer.cn/docs/gomianshiti/gomianshiti-1dd225t6esqld
go 爬虫
goquery
https://www.bbsmax.com/A/gVdngBm8zW/
爬虫小例子
https://topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/%E7%88%AC%E8%99%AB%E5%B0%8F%E6%A1%88%E4%BE%8B.html
goquery 百度热搜
https://zhuanlan.zhihu.com/p/264823205
package main
import (
"fmt"
"github.com/PuerkitoBio/goquery"
"log"
"net/http"
"os"
)
func BaiduHotSearch() {
res, err := http.Get("http://www.baidu.com")
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
if res.StatusCode != 200 {
log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
}
doc, err := goquery.NewDocumentFromReader(res.Body)
if err != nil {
log.Fatal(err)
}
doc.Find(".s-hotsearch-content .hotsearch-item").Each(func(i int, s *goquery.Selection) {
content := s.Find(".title-content-title").Text()
fmt.Printf("%d: %s\n", i, content)
})
}
func brickowl() {
var url string
//url = "https://www.brickowl.com/catalog/lego-sets"
url = "https://www.baidu.com/sugrec?prod=pc_his&from=pc_web&json=1&sid=36558_36625_36255_36822_36973_36413_36954_36166_36917_36919_36746_26350_36931&hisdata=%5B%7B%22time%22%3A1659258458%2C%22kw%22%3A%22rsync%20%E8%BF%9C%E7%A8%8B%E5%90%8C%E6%AD%A5%E5%88%B0%E6%9C%AC%E5%9C%B0%22%7D%2C%7B%22time%22%3A1659258458%2C%22kw%22%3A%22python%20logging%20%E5%A4%9A%E6%96%87%E4%BB%B6%22%7D%2C%7B%22time%22%3A1659258458%2C%22kw%22%3A%22python%20%E4%B8%89%E5%85%83%E8%A1%A8%E8%BE%BE%E5%BC%8F%22%7D%2C%7B%22time%22%3A1659258458%2C%22kw%22%3A%22%E5%A5%A5%E7%89%B9%E6%9B%BC%22%7D%2C%7B%22time%22%3A1659258458%2C%22kw%22%3A%22%E8%B6%85%E6%97%B6%E7%A9%BA%E5%A4%A7%E5%86%B3%E6%88%98%22%7D%2C%7B%22time%22%3A1659258458%2C%22kw%22%3A%22request%20%E8%8E%B7%E5%8F%96%20https%22%7D%2C%7B%22time%22%3A1659258458%2C%22kw%22%3A%22request%20%E8%8E%B7%E5%8F%96%20443%22%7D%2C%7B%22time%22%3A1659258458%2C%22kw%22%3A%22python3%20%E4%B8%8B%E8%BD%BDhttps%22%7D%2C%7B%22time%22%3A1659258458%2C%22kw%22%3A%22%E6%9C%AC%E5%9C%B0%E6%9C%8D%E5%8A%A1app%20%E7%9F%A5%E4%B9%8E%22%7D%2C%7B%22time%22%3A1659258458%2C%22kw%22%3A%22%E4%B8%93%E4%B8%9A%E9%A2%86%E5%9F%9F%E7%9F%A5%E8%AF%86%E5%9B%BE%E8%B0%B1%22%7D%5D&_t=1659258457505&req=2&bs=goland%20%E5%88%87%E6%8D%A2%E7%AA%97%E5%8F%A3%E5%BF%AB%E6%8D%B7%E9%94%AE&csor=0"
request, err := http.NewRequest("GET", url, nil)
fmt.Println(err)
request.AddCookie(&http.Cookie{Name: "SSESS96636da61f62e4e8dc28f1bac0edf597", Value: "EDilyyzHY_b5PNhdazL_d_mGIaCzAYcmdRChrKwyd9c"})
client := &http.Client{}
response, err := client.Do(request)
if err != nil {
fmt.Println(err.Error())
os.Exit(0)
}
defer response.Body.Close()
doc, err := goquery.NewDocumentFromReader(response.Body)
fmt.Println(doc.Text(), err)
}
func main() {
BaiduHotSearch()
}
思考
怎么理解 工具大于约定和文档?
怎么理解 约束做一件事只有一种方式?
怎么理解 面向故障编程?
链路跟踪?
持续
go官方文档
go 学习文档
八股文
每日一库
为什么说做好微服务很难
gozero项目示例
gozero商城
gozero
安装
go-zero
goctl 命令行工具
protoc protobuf 编辑器
安装文档参考
https://github.com/Mikaelemmmm/go-zero-looklook/blob/main/deploy/script/gencode/gen.sh
go install github.com/zeromicro/go-zero/tools/goctl@latest
查看gopath下
/Users/chenhaohao/go/bin/goctl
加入环境变量
vi ~/.bash_profile # mac还需配置 vi ~/.zshrc 添加 source ~/.bash_profile 才会生效
export PATH=$PATH:/Users/chenhaohao/go/bin
source ~/.bash_profile
protoc
https://github.com/protocolbuffers/protobuf/releases
https://github.com/protocolbuffers/protobuf/releases/tag/v21.4
mac
protoc-21.4-osx-x86_64.zip
wget https://github.com/protocolbuffers/protobuf/releases/download/v21.4/protoc-21.4-osx-x86_64.zip
cd
mv protoc ~/go/bin/s
chmod 777 protoc
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
快速开始
$ mkdir go-zero-demo
$ cd go-zero-demo
$ go mod init go-zero-demo
$ goctl api new greet
$ go mod tidy
vim greet/internal/logic/greetlogic.go
func (l *GreetLogic) Greet(req *types.Request) (*types.Response, error) {
return &types.Response{
Message: "Hello go-zero",
}, nil
}
$ cd greet
$ go run greet.go -f etc/greet-api.yaml
http://localhost:8888/from/you