目录
- 进程、线程、协程的区别?
- goroutine 相比线程的优势?
- go 与 Java 的区别?
- go 如何实现继承?
- init 函数什么时候执行?
进程、线程、协程的区别?
进程:进程是每一次程序动态执行的过程,是程序运行的基本单位。进程占据独立的内存,有内存地址和自己的堆,挂靠与操作系统。操作系统以进程为单位进行资源分配,进程是资源分配的最小单位。
线程:线程又称轻量级线程,是 CPU 调度的最小单元。线程隶属于进程,是程序的实际执行者,一个进程至少包含一个主线程,可以有多个子线程。线程会共享所属进程的资源,同时线程拥有自己的独占资源。线程切换和线程间通信主要通过内存共享(比如加锁),上下文切换很快(因为同一个进程内的线程共享资源),资源开销较少,但是相比于进程而言不够稳定,容易丢失数据。
协程:协程是一种用户态的轻量级线程,协程的调度完全由用户控制。一个线程可以有多个协程,协程不被操作系统内核管辖,而是由用户控制。
区别:
- 资源占用:进程是操作系统分配资源的最小单位,因此线程本身不占有资源,它可以访问其所隶属的进程的资源。进程所维护的是程序所包含的静态资源,比如:地址空间、打开的文件句柄集、文件系统状态、信号处理等;线程维护的是与程序运行相关的资源,即动态资源,包括:运行栈、调度相关的控制信息、待处理的信号集等。【或者说,进程维护的是资源,线程维护的是状态】
- 并发性:不仅进程可以并发,同一个进程的多个线程也可以并发。
- 系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。但是进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个进程死掉就等于所有的线程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。
- 协程和线程:协程避免了无意义的调度,由此可以提高性能,但是用户调度的过程中存在风险。
goroutine 相比线程的优势?
Goroutine 相较于传统线程而言有以下优点:
- 轻量级内存占用,goroutine 的初始栈为 KB 级别,可动态拓展,而线程栈通常在 MB 级别,因此使用 goroutine 可以轻松创建百万级的 goroutines;
- 快速创建销毁:goroutine 的创建耗时约为 300ns,且由于完全由用户态管理,故无系统调用的开销;
- 高效调度机制:单个线程可处理数万 goroutine,上下文切换成本约 200ns;
- 智能处理阻塞:goroutine 阻塞时,运行时会自动创建其它新线程处理其它 goroutine,避免线程级阻塞导致的资源浪费;
- 通信更安全:goroutine 采用原生 channel 替代共享内存;
- 资源利用率优化;
典型对比数据:
- 10万并发连接:Goroutine 消耗≈200MB内存,线程模式需要≈100GB;
- 上下文切换吞吐量:Go 调度器比 OS 调度器高 5-10倍;
go 与 Java 的区别?
- 运行:go 是静态编译语言;Java 基于类的面向对象语言,Java 应用程序在 JVM 上运行。
- 函数重载:go 上不允许函数重载,必须具有方法和函数的唯一名称;java 允许函数重载。
- 多态:Java 默认允许多态,而 go 没有。
- 路由配置:go 语言使用 HTTP 协议进行路由配置;java 使用 Akka.routing 进行路由配置。
- 继承:go 的继承通过匿名组合完成(也就是 struct 当中的嵌入),基类以 Struct 的方式定义,子类只需要把基类作为成员放在子类的定义中,支持多继承;Java 的继承通过 extends 关键字完成,不支持多继承。
go 如何实现继承?
通过 struct 当中的嵌入来实现类似于继承的效果。
init 函数什么时候执行?
特点:
- init函数先于main函数自动执行,不能被其他函数调用。
- init函数没有输入参数、返回值。
- 每个包可以有多个init函数,包的每个源文件也可以有多个init函数。
- go没有明确定义同一个包的init执行顺序,编程时程序不能依赖这个执行顺序。
- 不同包的init函数按照包导入的依赖关系决定执行顺序。
作用:
- 初始化不能采用初始化表达式初始化的变量。
- 程序运行前的注册。
- 实现sync.Once功能。
执行顺序:
go程序初始化先于main函数执行,由runtime进行初始化,初始化顺序如下:
- 初始化导入的包,包的初始化顺序并不是按导入顺序执行的,runtime需要解析包依赖关系,没有依赖的包最先初始化。
- 初始化包作用域的变量,runtime解析变量依赖关系,没有依赖的变量最先初始化。
- 执行包的init函数。
最终的初始化顺序:变量初始化 → \rightarrow → init()
→ \rightarrow → main()
。