语言进阶与依赖管理|青训营笔记

31 阅读6分钟

这是我参与[第五届青训营]伴学笔记创作活动的第2天

一、语言进阶(进程&线程协程)

1.1进程

  • 进程是程序一次动态执行的过程,是程序运行的基本单位。
  • 每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。
  • 进程占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、页表、文件句柄等)比较大,但相对比较稳定安全。

1.2线程

  • 线程又叫做轻量级进程,是CPU调度的最小单位。
  • 线程从属于进程,是程序的实际执行者。一个进程至少包含一个主线程,也可以有更多的子线程。
  • 多个线程共享所属进程的资源,同时线程也拥有自己的专属资源。
  • 程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。

1.3协程

  • 协程是一种用户态的轻量级线程,协程的调度完全由用户控制。
  • 一个线程可以拥有多个协程,协程不是被操作系统内核所管理,而完全是由程序所控制。
  • 与其让操作系统调度,不如我自己来,这就是协程

2.1区别(线程与进程|协程与线程)

-主线程:物理线程,直接作用在cpu上。重量级,非常消耗cpu资源
-协程:轻量化线程,逻辑态,资源消耗小

》线程与进程的区别:

  1. 地址空间:线程是进程内的一个执行单元,进程内至少有一个线程,它们共享进程的地址空间,而进程有自己独立的地址空间
  2. 资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
  3. 线程是处理器调度的基本单位,但进程不是
  4. 二者均可并发执行
  5. 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制

》协程与线程的区别:

  1. 一个线程可以多个协程,一个进程也可以单独拥有多个协程。
  2. 线程进程都是同步机制,而协程则是异步。
  3. 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。
  4. 线程是抢占式,而协程是非抢占式的,所以需要用户自己释放使用权来切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力。
  5. 协程并不是取代线程, 而且抽象于线程之上, 线程是被分割的CPU资源, 协程是组织好的代码流程, 协程需要线程来承载运行, 线程是协程的资源, 但协程不会直接使用线程, 协程直接利用的是执行器(Interceptor), 执行器可以关联任意线程或线程池, 可以使当前线程, UI线程, 或新建新程。
  6. 线程是协程的资源。协程通过Interceptor来间接使用线程这个资源。

介绍.jpg

》特点:主线程结束(不管协程是否结束)程序退出

  • M:操作系统的主线程
  • P:协程执行需要的上下文
  • G:协程

2.2代码&介绍

  1. strconv.Itoa(i) //转化为string
  2. Time.sleep(d duration)//方法会阻塞一个协程的执行直到d时间结束。
  3. 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介绍

  1. channel本质就是一个队列
  2. 数据是先进先出(FIFO : first in first out)
  3. 线程安全,多 goroutine 访问时,不需要加锁,就是说 channel 本身就是线程安全的
  4. channel 是有类型的,一个 string 的 channel 只能存放 string 类型数据
  5. 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

  1. $GOPATH【无法实现package的多版本控制】
  • bin 项目编译的二进制文件
  • pkg 中间产物,加速编译
  • src 项目源码
  • 项目代码直接依赖 src下的代码
  • go get 下载最新版本的包到 src 目录下
  1. Go Vendor【无法控制依赖版本,更新版本会导致依赖冲突】
  • 项目目录增加Vendor文件,所有依赖包副本形式放在$ProjectRoot/vendor
  • 依赖寻址方式:vendor=>GOPATH
  • 【解决多个项目需要同一个package依赖的冲突问题】
  1. Go Module
  • 通过 go.mod 文件管理依赖包版本
  • 通过go get /go mod 指令工具管理依赖包
  • 【目标:定义版本规则和管理项目依赖关系】
  1. 依赖管理三要素
  • 配置文件,描述依赖 go.mod
  • 中心仓库管理依赖库 Proxy
  • 本地工具 go get/mod
  1. 依赖配置- indirect
  • A->B->C
  • A->B 直接依赖
  • A->C 间接依赖
  • 【选择最低的兼容版本】
  1. 依赖分发 -回源 -Proxy -变量 GOPROXY
  • 无法保证构建稳定性:增加/修改/删除软件版本
  • 无法保证依赖可用性:删除软件
  • 增加第三方压力:代码托管平台负载问题
  • Proxy 1 => Proxy 2 => Diret
  1. 工具 -go get
  • @update 默认
  • @none 删除依赖
  • @v1.1.2 tag版本,语义版本
  • @23dfdd5 特定的commit
  • @master 分支的最新commit 8.工具 -go mod
  • init 初始化,创建go.mod文件
  • download 下载模块到本地缓存
  • tidy 增加需要的依赖,删除不需要的依赖

参考文章

进程、线程、协程

WaitGroup 基本用法和如何实现以及常见错误