cpper 转 Golang

发布于:2025-06-04 ⋅ 阅读:(32) ⋅ 点赞:(0)

在这里插入图片描述

推荐视频 8小时转职Golang工程师(如果你想低成本学习Go语言)

与C++对比

C++20引入了【协程】,但本身并不自带调度器,也没有像 Go 一样的原生 channel 和调度机制。(可借助第三方库)

虽然 C++ 功能强大、生态丰富,但 Go 的定位与目标用户群完全不同。Go 并不是为了取代 C++,而是为了解决 C++ 在某些场景下的“复杂性、效率开发难题”。


其他Go优点:

  1. 编译上:Go是模块编译
  2. 官方内置依赖管理
  3. Go原生自带GC自动管理内存
  4. Go 自带跨平台编译能力(在windows上编译Linux程序)
    Go 默认会把所有依赖打包成一个单一的可执行文件,不依赖系统库、共享库、运行时环境等。
  5. 虽然 Go 没有虚拟机,但它有自己的 runtime,内嵌在最终编译生成的可执行文件中。(不像 Java 的 JVM 那样独立存在。)包括:协程调度器(M:N 的 goroutine 调度),内存管理(GC),通道通信(channel),时间管理(定时器、sleep 等),系统调用封装(syscall 层)。

Go语言

在这里插入图片描述
2009年,Google公开Go语言。

Go 的爆红,与它适合的场景有关:

  • 云原生开发(Kubernetes 就是用 Go 写的)
  • 微服务后端(如 Docker、etcd、Consul)
  • 高并发网络服务
  • DevOps 工具(如 Terraform、Prometheus)

基本语法

特殊类型与占位符

  • _ 代表 匿名变量

  • %T 类型

:=

//(只能在函数体内部使用,并且不能声明已存在的变量)
x := 10        // 自动推导类型为 int
name := "Tom"  // 自动推导类型为 string

// 等价:
var x int = 10
var name string = "Tom"
	data, ok :=


	if data, ok := ; ok{}

defer

数组 slice切片

make([]int, 3, 5)

numbers[1:4]

map[string]string

struct 结构体

只有对外包的公开和隐藏,没有 private 等复杂属性。

type Book struct{
	title string	//属性名 小写只能在本包内访问
	auth string
}

// this是调用该方法对象 的拷贝
func (this Book) Show(){	//方法名大写,对外可见(导出)
	fmt.Println("Nmae = ", thie.Name)
}

func (this Book) GetTitle() string{
	return thie.Name
}


func (this *Book) SetTitle(newTitle string){
	this.Name = newTitle
}

//
func main(){
	book := Book(Title: "shu1", auth: "zhang3")
	
}

给函数可以传结构体指针,但是函数内部还是正常通过 . 访问成员

继承

type SuperBook struct{
	Book //SuperBook类继承了上面Book类的方法
	
	level int
}

// 可以重定义方法

//
func main(){
	//两种构造
	s := SuperBook(Book{"name","auth"}, 18)

	var s2 Book
	s2.name = "name2"
}

interface 多态

“只要你实现了这些方法,你就是这个类型。”

interface 只能定义方法集,不能拥有成员变量。
本质:

// Go 的 interface 是一个结构体,底层表示可以抽象为两个字段:
type iface struct {
    tab  *itab    // 指向方法表(type + 实现)
    data unsafe.Pointer // 指向实际的数据
}

形式一:接口指针

package main

import "fmt"

//接口 本质是一个指针
type AnimalIF interface{
	Sleep()
	GetType() string // 获取动物种类
}
//【只有把该接口的所有方法都实现的类,才能通过接口指针调用对应的类方法】

type Cat struct{
	type Color
}

func (this *Cat) Sleep(){
	fmt.Println("Cat is sleeping")
}

func (this *Cat) GetType() string{
	return "Cat"
}

//多态
func main(){
	var animal AnimalIF //父类指针 接口数据类型
	
	animal = &Cat{"Green"}
	
	animal.Sleep() //调用的就是Cat的Sleep()方法
}

形式二:函数传递接口指针参数

// 同上类

func showAnimal(animal AnimalIF){
	animal.Sleep()
	fmt.Println("type = ", animal.GetType()) //直接调用【具体对象】的方法了
}

interface 空接口可作万能类型:

在这里插入图片描述
上图代码:

package main

import "fmt"

func myFunc(arg interface{}) {
	fmt.Println("myFunc is calling:", arg)
}

type Book struct {
	auth string
}

func main() {
	book := Book{"zhang3"}

	myFunc(book)
	myFunc(1234)
	myFunc("go学长")
}

interface类型的空接口 断言

(必须是空接口) 原理是反射

arg.(<类型>)

在这里插入图片描述

机制

调度器

Go 的调度器(Scheduler)是其运行时系统最核心的组件之一,它决定了 Go 协程(goroutine)如何被调度和运行在操作系统线程(OS thread)之上。Go 的高并发能力,核心就在于它这个 “M:N 调度器” 的高效实现。

GPM:

M:Machine,操作系统线程(OS thread)
P:Processor,执行 goroutine 的上下文资源,维护任务队列。
G:Goroutine,轻量级协程,即你的 Go 函数

  • M:
    Go 运行时有一个 M 的空闲池(runtime.mcache),执行完 G 的 M 会归还,不会频繁创建销毁。
    一般来说,一个 Go 程序会根据 并发情况、GOMAXPROCS 设置、syscall 阻塞等情况,创建 数个到几十个 M(线程)。在高并发、IO 密集程序中,可能会增长到上百个甚至更多。

  • P:
    P 维护了队列、内存、定时器等所有支撑 goroutine 正常运行的资源,这样线程切换不会带来大量资源迁移。
    P 维护自己的 goroutine 队列 - runq (通常为 256 个 G)。新的 goroutine 创建后,优先放入当前 P 的 runq;如果本地 runq 满了,会将一半 goroutine 转移到全局队列

  • G:

调度过程:

  1. go func() 创建一个G,加入当前 上下文P 的本地任务队列
  2. 每个 上下文P 会绑定一个 线程M,M从P的任务队列中取G执行。
  3. 抢占机制:Go 运行时调度器会定期抢占长时间运行的 G,防止“霸占 CPU”。
  4. 均衡调度算法:工作窃取(work stealing):如果某个 P 的 G 队列空了,它会从其他 P 那里“偷”任务。
  5. 系统调用的处理:如果某个 G 发起了 syscall(如文件读写、网络 IO),其所在的 M 会被释放,P 绑定新的 M 来继续执行别的 G(防止阻塞)。
M:N 调度器(多对多调度器)

M 个用户级线程(协程 G)调度到 N 个内核线程(OS 线程 M)上运行。

让成千上万个轻量任务(G)能够灵活、高效地运行在少量线程(M)上,由 P 统一协调,让开发者无需关心线程切换、调度和资源浪费问题。

runtime

在 Go 中,遇到系统调用(如 I/O 操作)时,会进入 runtime,由 runtime 进行管理和调度处理

如网络IO:

  1. G 发起 syscall(如 read(fd, buf))
runtime.entersyscall()
do actual syscall
runtime.exitsyscall()
  1. runtime 标记该 G 为 syscall 状态
    当前的 M 被认为“没有 G 在运行”,所以可以解绑 P;P 立刻找别的 G 调度运行,不阻塞。
  2. syscall 返回后,runtime 恢复调度
    G标记恢复,重新被放入 P 的 runqueue,等待调度执行

全局G队列

Go 的 runtime 中,除了每个 Processor(P)拥有自己的本地 run queue(局部 G 队列),还有一个全局 G 队列(global run queue),用于整个调度器协调 goroutine 的执行负载。

  • 一些早期创建的 G 没有绑定具体的 P,只能先挂在全局队列;
  • 某个 P 的 runqueue 空时可以从全局队列偷 G;
  • runtime 会将 G 放回全局队列,避免调度干扰;系统后台线程唤醒的 G,会加入全局队列;

runtime/proc.go

type schedt struct {
    ...
    runqhead uint32
    runqtail uint32
    runq     [gQueueSize]*g
    ...
}
var sched schedt

反射

reflect 包

变量的结构 pair:
在这里插入图片描述

获取 Type 和 Value (字段必须导出(大写首字母),无论是否在同一个包内。Go 的 reflect 包是语言级别的 API,它不区分“是否同包”)

t := reflect.TypeOf(obj)	//返回一个表示 obj 类型的 reflect.Type 对象。
v := reflect.ValueOf(obj)	//返回一个表示 obj 值的 reflect.Value 对象。
field := v.FieldByName("Name") // 尝试通过字段名 "Name" 查找并返回对应的 reflect.Value。
fmt.Println(field.Interface())

channel

c := make(chan int ,3) // 创建一个缓冲区为 3 的 channel

c <- 1   	// 发送,不阻塞(因有缓冲)
x := <-c 	// 接收,若为空会阻塞

close(c)	// 关闭 channel 后,可以读不可以写。

for range 读取 channel:

ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)

for val := range ch {	// ❗若不关闭 channel,range 会一直阻塞等待新数据。
	fmt.Println(val)
}
// 输出:1, 2, 3

select 同时监听多个 channel,每个 case 是一个 channel 操作。

select {
case v := <-c1:
    fmt.Println("from c1", v)
case c2 <- 100:	// 满足条件:c2 有缓冲且还有剩余空间 或者 无缓冲并有 goroutine 正在读
    fmt.Println("send to c2")
default:
    fmt.Println("no case ready")
}

nil channel 的读写操作都会永久阻塞!


依赖

C:\Users\Administrator>go env
set GO111MODULE=on
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\Administrator\AppData\Local\go-build
set GOENV=C:\Users\Administrator\AppData\Roaming\go\env
set GOEXE=.exe
set GOEXPERIMENT=
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMODCACHE=C:\Users\Administrator\go\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\Administrator\go
set GOPRIVATE=
set GOPROXY=https://goproxy.io,direct
set GOROOT=F:\2024\my_homework\gwsj\go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLCHAIN=auto
set GOTOOLDIR=F:\2024\my_homework\gwsj\go\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.23.1
set GODEBUG=
set GOTELEMETRY=on
set GOTELEMETRYDIR=C:\Users\Administrator\AppData\Roaming\go\telemetry
set GCCGO=gccgo
set GOAMD64=v1
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=NUL
set GOWORK=
set CGO_CFLAGS=-O2 -g
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-O2 -g
set CGO_FFLAGS=-O2 -g
set CGO_LDFLAGS=-O2 -g
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=C:\Users\ADMINI~1\AppData\Local\Temp\go-build3313090824=/tmp/go-build -gno-record-gcc-switches

GOPROXY

指定项目第三方依赖库的下载地址

GOSUMDB 会在 拉取模块时校验,防篡改
用 GOPRIVATE 指定为私有仓库,不会进行GOPROXY下载校验

GOPATH

在 Go1.11 之前,Go 没有“官方”的依赖管理系统,社区一开始用 GOPATH 方式,所有项目必须在 GOPATH,而且无法指定版本,永远下载最新版

GOModules

C++需要自己去下载,指明库的路径 (C++ 没有“官方的行李打包规范”,只能自己想办法。)
但是Go Modules 只要说清楚,系统自动做。

Go Modules 就是 Go 的“包管理工具”。

C:\Users\Administrator>go mod
Go mod provides access to operations on modules.
..

工作流程:

  • go mod init myproject 这会生成一个 go.mod 文件,像身份证一样告诉 Go:这是一个模块项目。
  • 写代码时用了别人的包:import “github.com/gin-gonic/gin”
    只需 go get github.com/gin-gonic/gin@v1.9.0,Go就会自动去下载,更新 go.mod、go.sum,并把下载的代码都缓存到 $GOPATH/pkg/mod
    • go.mod记录你用了哪些包、用了哪个版本。
    • go.sum:记录每个包的校验值,保证别人能下载到 完全一致 的版本。

Go Modules 会自动处理依赖树。比如你用了 A,A 又用了 B 和 C,Go 会自动把 B 和 C 也拉下来,并确保不会冲突


erlang?

Erlang 是军用级飞船,出了故障能自动重启继续飞;
Go 是工程师造的卡车,好开,但出了问题你得下车修。


网站公告

今日签到

点亮在社区的每一天
去签到