Golang与C#技术栈的核心对比
学习新知识,我的策略是模仿-->归纳--->举一反三,
本次记录入局Golang,6月龄必知必会,形式是同我的主力语言C#做对比。
1. 宏观预览

1.1 常见结构对比
| go语言 | --- | C#语言 | --- |
|---|---|---|---|
| module | assemble | ||
| package | go get github.com/thoas/go-funk | namespace | Install-Package Masuit.Tools.Core |
| struct | class、struct | ||
| pointer | reference | ||
| net/http | web脚手架、 httpclient | kestrel、httpclient | |
| net/http/DefaultServeMux | ASP.NETCore脚手架路由 | ||
| goroutine | go语法糖 | 基于线程池的异步任务 | async/await语法糖 |
| channel | CSP | TPL data flow | CSP模型在C#并非主流 |
| context | timeout、 cancellation-token |
1.2 访问级别
go语言使用首字母大小写来体现公开/私有, 应用到package/struct/function;
C#显式使用关键字来体现, public、protected、private
1.3 类型初始化
go语言有两个初始化的内置关键字
- new : 用于分配内存(带内存零值),返回指针 new(int), new(Cat)
- make : 只用于slice、map、 channel 引用类型的初始化
也可以使用字面量初始化:
person := Person{
Name: "Alice",
Age: 30,
City: "New York",
}
C#基础类型使用字面量, 引用类型使用new关键字
2. 面向对象
同样是面向对象编程语言,go用结构体来体现,倾向于组合;
C#常用类来体现,现代化的显式面向对象。
封装
go基于结构体struct和接收者函数来封装事物和行为。
接收者函数分为: 值接收者函数、指针接收者函数。
-
指针接收者函数内的操作会体现到入参。
-
不管是值,还是指针,都能调用指针接收者函数/值对象接受者函数,效果还是如上一点一致。
C# 显式使用Class struct等结构来封装数据和行为。
抽象/继承
go没有显式抽象函数、抽象类的做法,鸭子模式体现接口、 组合体现父子类继承关系。
接口将具有共性的方法放在一起,其他任何类型只要实现了这些方法就是实现了接口,俗称鸭子模式。
C#具备语义化的继承/抽象/多态, 显式继承。
3. 类型和内存分配
go是C语言家族
指针跟其他基础类型一样都是一等公民;
其他我们认为有引用类型效果的常见对象:string、slice、map、channel要么是struct+pointer的复合类型、要么本身就是指针的别名。
基础类型 : int long double char rune bool struct array pointer
golang 日常唱双簧的是指针和其他基础类型,
只看
var p = new(Point) 或者
p2 := Person{
Name: "Alice",
Age: 30,
City: "New York",
}
并不决定p所指向的对象是分配在堆还是栈。
go不像java .NET明确地根据类型划分堆栈存储, 取决于编译器的逃逸分析:
如果编译器能推断变量的生命周期没有超过函数的作用域,就定义在栈上; 如果对象的大小超过阈值,或者变量的指针被返回或者被存储到其他地方,导致其生命周期超出了当前作用域,编译器会将其分配到堆上。
C#的类型划分更为现代化
严格划分值类型、引用类型,
- 值类型: int long double char struct
- 引用类型: string array List<> Dictionary
这个“严格” 体现在值类型分配在栈上, 引用类型分配在堆上。
go的指针弱化了C语言的指针操作,go指针的作用仅操作其指向的对象, 不能基于地址这个概念做指针移位, 也不能基于地址这个概念做类型转化。
从这个意义上看,C#的引用等价于go的指针, 都是类型安全的指针。
另一方面, 两种语言都提供了对内存进行任意读写的姿势(非代码安全)。
go的unsafe.Pointer本质是一个int指针,
C# unsafe关键字可用在函数、属性、构造函数、代码块。
4. 异步编程的实现
异步: 在核心执行流上具备开启新任务的能力。
两者给到开发者的编程实践都比较一目了然: “以同步的思绪流开启异步任务”。
| 异步编程 | go | C# |
|---|---|---|
| 编程实践 | go语法糖(函数可以有形参,返回值会被忽略) | async/await语法糖(有传染性) |
| 底层原理 | 用户态轻量级线程,基于GPM调度模型 | 行为状态机,有默认的任务调度器 |
| 目标倾向 | 利用用户态线程尽量避免线程切换 | 常规线程池调度 |
| 适合 | 互联网高并发、负载的、短小任务 | 机制稳定、生态繁荣 |
核心调度流程图
核心认知
线程是cpu调度的基本单位,不管是goroutine还是async-wait机制都是在尝试提高cpu的利用率。
对比传统的利用线程池做辗转腾挪,goroutine提供了轻量级用户态线程和便利的并发能力,调度开销低,适合高并发的小任务场景, 天然适配互联网高并发的、复杂的、小任务请求响应模型。
但是如果是纯CPU密集的请求响应模型, golang在传统线程之外新增用户态调度未必就比java .net 等传统异步模型要优。
5. GC
GC 只负责回收堆对象;
栈对象在函数返回时被清空,但是栈上对象可能引用了堆上对象。
GC从root根对象出发,root对象:全局变量和栈区变量。
5.1 golang GC: 三色并发标记、清除
go 在1.3 之前是标记/清除算法,需要stw;
在1.4引入了三色标记/清除算法, 需要stw;
在1.5 引入了混合写屏障,现在是三色并发标记/清除, stw只存在开关写屏障阶段,耗时很短。
什么叫混合写屏障
- 栈区出发的对象全为黑色, 栈区新增的也为黑色
- gc过程中 新增的对象、删除的对象均为灰色。
1.5之后的gc
- 开启混合写屏障
- 三色并发标记: 存在白、灰、黑三集合,初始全为白色,下游节点被触达,下游标记为灰色,自身标记为黑色。
- 清除
- 关闭写屏障
触发时机
- 定时触发: 默认2min
- 内存主动触发: 自上次GC之后累计申请的堆内存达到阈值触发GC,阈值是上次gc后堆内存分配量的两倍, 由GOGC 这个参数控制。
- 手动触发:
runtime.GC()函数
5.1 C# GC是基于分代策略的标记压缩
标记压缩
标记 :一开始假设所有的对象都是垃圾,然后对栈上变量、全局变量、静态变量这些根进行扫描, 标记活跃对象;
清理: 不可达对象即为可回收;
压缩: 并不是我们理解的占用内存减少,更像一种磁盘清理,将非垃圾对象移动到占有较大空间的连续垃圾对象区域。
分代策略, 触发时机
C# GC中包含“代”的机制,目的是为了提升性能,垃圾收集就是在第0代满的时候发生的(还不足,就会去清理第1代)。
- 第0代: 新申请的对象、往往短命, 这一代清理会很频繁。
- 第1代: 从第0代晋升的对象, 清理频率稍低一点。
- 第2代: 从第1代晋升的对象,清理频率最低。

触发时机:第0 代满的时候,一般是512k;
本文来自博客园,作者:{有态度的马甲},转载请注明原文链接:https://www.cnblogs.com/JulianHuang/p/16825391.html
欢迎关注我的原创技术、职场公众号, 加好友谈天说地,一起进化

浙公网安备 33010602011771号