这是我参与[第五届青训营]伴学笔记创作活动的第2天
一、语言进阶(进程&线程协程)
1.1进程
- 进程是程序一次动态执行的过程,是程序运行的基本单位。
- 每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。
- 进程占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、页表、文件句柄等)比较大,但相对比较稳定安全。
1.2线程
- 线程又叫做轻量级进程,是CPU调度的最小单位。
- 线程从属于进程,是程序的实际执行者。一个进程至少包含一个主线程,也可以有更多的子线程。
- 多个线程共享所属进程的资源,同时线程也拥有自己的专属资源。
- 程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。
1.3协程
- 协程是一种用户态的轻量级线程,协程的调度完全由用户控制。
- 一个线程可以拥有多个协程,协程不是被操作系统内核所管理,而完全是由程序所控制。
- 与其让操作系统调度,不如我自己来,这就是协程
2.1区别(线程与进程|协程与线程)
-主线程:物理线程,直接作用在cpu上。重量级,非常消耗cpu资源
-协程:轻量化线程,逻辑态,资源消耗小
》线程与进程的区别:
- 地址空间:线程是进程内的一个执行单元,进程内至少有一个线程,它们共享进程的地址空间,而进程有自己独立的地址空间
- 资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
- 线程是处理器调度的基本单位,但进程不是
- 二者均可并发执行
- 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制
》协程与线程的区别:
- 一个线程可以多个协程,一个进程也可以单独拥有多个协程。
- 线程进程都是同步机制,而协程则是异步。
- 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。
- 线程是抢占式,而协程是非抢占式的,所以需要用户自己释放使用权来切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力。
- 协程并不是取代线程, 而且抽象于线程之上, 线程是被分割的CPU资源, 协程是组织好的代码流程, 协程需要线程来承载运行, 线程是协程的资源, 但协程不会直接使用线程, 协程直接利用的是执行器(Interceptor), 执行器可以关联任意线程或线程池, 可以使当前线程, UI线程, 或新建新程。
- 线程是协程的资源。协程通过Interceptor来间接使用线程这个资源。
》特点:主线程结束(不管协程是否结束)程序退出
- M:操作系统的主线程
- P:协程执行需要的上下文
- G:协程
2.2代码&介绍
- strconv.Itoa(i) //转化为string
- Time.sleep(d duration)//方法会阻塞一个协程的执行直到d时间结束。
- go 方法名() //开启一个协程
func test() {
for i := 1; i <= 100; i++ {
fmt.Println("Test hello,world" + strconv.Itoa(i)) //转string
time.Sleep(time.Second)//每个1秒后打印1次
}
}
func main() {
go test() //开启一个协程
for i := 1; i <= 10; i++ {
fmt.Println("main() hello,world" + strconv.Itoa(i)) //转string
time.Sleep(time.Second)
}
}
协程中输出1-100条语句,主线程中输出1-10条语句,协程和线程每秒打印1次,10s后主线程结束(协程未结束)程序依然退出。
二、Channel介绍&注意
1.1介绍
- channel本质就是一个队列
- 数据是先进先出(FIFO : first in first out)
- 线程安全,多 goroutine 访问时,不需要加锁,就是说 channel 本身就是线程安全的
- channel 是有类型的,一个 string 的 channel 只能存放 string 类型数据
- make(chan 元素类型,[缓冲大小])
- 无缓冲通道 make(chan int)
- 有缓冲通道 make(chan int,n)//n通道容量
package main
func CalSquare() {
src := make(chan int)
dest := make(chan int, 3)
// 子协程生产数字
go func() {
defer close(src) //延迟资源关闭
for i := 0; i < 10; i++ {
src <- i
}
}()
// 子协程计算输入数字的平方
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
}()
// 主协程输出最后的平方
for i := range dest {
println(i)
}
}
func main() {
CalSquare()
}
1.2注意
- channel 中只能存放指定的数据类型。
- channle 的数据放满后,就不能再放入了。
- 如果从 channel 取出数据后,可以继续放入。
- 在没有使用协程的情况下,如果 channel 数据取完了,再取,就会报 dead lock
三、WaitGroup
1.1介绍:
WaitGroup就是package sync用来做任务编排的一个并发原语。这个要解决的就是并发-等待的问题:现有一个goroutine A在检查点(chaeckpoint)等待一组goroutine全部完成,如果在执行任务的这些goroutine还没有全部完成,那么goroutine A就会阻塞在检查点,直到所有的goroutine都完成后才能继续执行。
1.2基本使用
func (wg *WaitGroup) Add(delta int)//设置WaitGroup的计数值;
func (wg *WaitGroup) Done()//用来将WaitGroup的计数值减1,其实就是调用了Add(-1);
func (wg *WaitGroup) Wait()//调用这个方法的goroutine会一直阻塞,直到WaitGroup的计数值变为0。
// 快速打印
func hello(i int) {
println("hello goroutine:" + fmt.Sprint(i))
}
func ManyDoWait() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
defer wg.Done()
hello(j)
}(i)
}
wg.Wait()
}
》依赖管理: GOPATH>GO VENDOR>GO Module
- $GOPATH【无法实现package的多版本控制】
- bin 项目编译的二进制文件
- pkg 中间产物,加速编译
- src 项目源码
- 项目代码直接依赖 src下的代码
- go get 下载最新版本的包到 src 目录下
- Go Vendor【无法控制依赖版本,更新版本会导致依赖冲突】
- 项目目录增加Vendor文件,所有依赖包副本形式放在$ProjectRoot/vendor
- 依赖寻址方式:vendor=>GOPATH
- 【解决多个项目需要同一个package依赖的冲突问题】
- Go Module
- 通过 go.mod 文件管理依赖包版本
- 通过go get /go mod 指令工具管理依赖包
- 【目标:定义版本规则和管理项目依赖关系】
- 依赖管理三要素
- 配置文件,描述依赖 go.mod
- 中心仓库管理依赖库 Proxy
- 本地工具 go get/mod
- 依赖配置- indirect
- A->B->C
- A->B 直接依赖
- A->C 间接依赖
- 【选择最低的兼容版本】
- 依赖分发 -回源 -Proxy -变量 GOPROXY
- 无法保证构建稳定性:增加/修改/删除软件版本
- 无法保证依赖可用性:删除软件
- 增加第三方压力:代码托管平台负载问题
- Proxy 1 => Proxy 2 => Diret
- 工具 -go get
- @update 默认
- @none 删除依赖
- @v1.1.2 tag版本,语义版本
- @23dfdd5 特定的commit
- @master 分支的最新commit 8.工具 -go mod
- init 初始化,创建go.mod文件
- download 下载模块到本地缓存
- tidy 增加需要的依赖,删除不需要的依赖