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

49 阅读6分钟

一、并发、并行的区别

并发是通过时间片的切换实现的,其本质依旧是使用一个核进行运算;

并行才是真正的大家所理解的含义,是同时有多个核进行运算,并行也可以理解成为实现并发的一种方法,这两者的含义上有着一定的区别,一定要加以区分。

Go语言实现了并发性能极高的调度模型,通过高效的调度,可以充分的发挥多核的优势,使其高效的运行。

二、程序、进程、线程、协程的区别

在操作系统这门课程中,程序、进程、线程是我们详细分析区别的一组概念,在本门课程中主要辨析线程与协程这两组概念的区别。

程序是用某种程序设计语言编写的,指示计算机或其他装置执行动作或做出判断的指令。

进程是系统进行资源分配和调度的基本单位,是操作系统结构的基础,是系统分配资源和调度的基本单位。

线程是进程中的一个实体,是被系统独立调度和分派的基本单位,是CPU调度和分派的最小基本单位,线程自己不拥有操作系统资源,但是该线程可与同属进程的其他线程共享该进程所拥有的全部资源。是内核态,线程可以并发的跑多个协程,栈MB级别

协程是Go语言为了实现高并发而设计的一个概念,是用户态,轻量级线程,栈KB级别

Go语言一次可以创建上万个协程,也是该语言可以实现高并发的原因。

三、开启协程的代码实现

例如:快速打印出hello goroutine :0 - hello goroutine :4

题目的关键在于快速,如果去掉这两个字,可以使用for循环实现。快速在本题的含义就是实现并发

func hello(i int) {
    println("hello goroutine : " + fmt.Sprint(i))
}
func HelloGoRoutine() {
    for i := 0; i < 5 ; i++ {
        go func (i int) {
            hello(j)
        }(i)
    }
    time.Sleep(time.Second)
}

Go语言中实现协程,只需在函数名前面加上go关键字即可;

代码的最后一句, time.Sleep(time.Second),是为了保证在子协程没有结束前,主协程不退出,除此以外,还有其他的方式可以实现,见第六点。

四、协程间的通信

Go提倡通过通信来共享内存,而不是通过共享内存来实现通信,但Go也保存着通过共享内存实现通信的机制。

通过通信来共享内存需要使用的是通道,即channel,可以保证输入数据的顺序;

通过共享内存实现通信,实现数据交换,使用的是临界区,需要使用互斥量进行加锁,需要有临界区的权限,这种方法可能在一定程度上影响程序的性能。

五、Channel的一些操作

5.1 channel是一种引用,通过make关键字创建,即 

    makechan 元素类型,[缓冲区大小])

有缓冲通道:

    make(chan int ,2)

无缓冲通道(也被称为同步通道):

    make (chan int)

为解决同步的问题,就可以使用有缓冲通道,通道的容量就是表示通道中能存有多少元素。

有缓冲通道的使用类似于生产消费模型。通常来说,消费者的模块更加复杂,取走的速度更慢,生产者的更容易,生产速度更快,使用了有缓冲通道就不会因为消费者的慢速影响到生产者的效率,可以解决两者速度不匹配的问题。

Go也可以通过共享内存实现通信的机制,此时需要使用Lock(),Unlock(),如果不加锁,会发生并发安全问题,因此在实际的项目开发中,应尽量避免对共享内存进行一些可能发生并发安全问题的一些操作。

六、WaitGroup实现同步

Add(delta int )    计数器加delta
Done()             计数器-1
Wait()             阻塞直到计数器为0

上述例题的更优化的方式:

func ManyGoWait() {
    var wg sync.WaitGroup
    wg.Add( delta: 5)
    for i:= 0; i < 5 ; i++ {
        go func(j int){
            defer wg.Done()
            hello(j)
        }(i)
    }
    wg.Wait()
}

七、依赖管理

在开发项目时,利用他人已经封装好的、经过验证的开发组件或工具提升研发效率。

实现hello world程序或其他单体函数时,只依赖原生的sdk即可;在实际的开发过程中,不可能基于标准库搭建,会更专注业务逻辑的实现,框架framework、日志log、dirver等一系列的依赖可以通过sdk的方式引入,因此对于依赖包的管理比较重要。

7.1 Go的依赖管理阶段

三个阶段:GOPATH    Go Vendor     Go Moudle

最常用的是第三种,三种方式的使用依据主要根据以下两点进行选择:

1.不同环境(项目)依赖的版本不同

2.控制依赖库的版本

7.1.1 GOPATH

GOPATH是GO语言支持的一个环境变量,是一个工作区,

该目录下有三个关键点

1. bin:项目编译生成的二进制文件,存放在该目录下;

2. pkg:(package)是项目编译的中间产物,用于加速编译;

3. src:项目源码,项目工程都在src下。

GOPATH的实现逻辑是:所有的项目代码都依赖src下的代码,要通过go get 下载最新版本的包到src目录中,但是这种方法有一个弊端。

GOPATH的弊端无法实现package的多版本控制

eg:本地的不同项目A,B,依赖于同一个pkg的不同版本,A-V1版本,B-V2版本,无法实现package的多版本控制

V1实现了项目A使用的方法func A(),V2实现了项目B使用的方法 func B(),但是V2没有做到兼容,可能把func A()删除了,对于我们的本地项目,依赖的是同一个src源码,这样会导致两个程序不能同时构建成功

7.1.2 Go Vendor

在项目目录下多一个vendor文件夹存放副本,每一个项目都有一个所使用依赖包的副本,在寻找依赖时先从vendor开始找,如果没有再寻找GOPATH。

Go Vendor:通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突

如上个例子,A项目的vendor下是V1版本,B项目的vendor下是V2版本

Go Vendor弊端:仍旧是依赖项目源码,无法控制依赖的版本,更新项目时有可能出现依赖冲突,导致编译错误

eg:A项目同时依赖package B和C,  B依赖package D 的V1版本,C依赖package D 的V2版本,但是package D 的V1版本和V2版本不兼容

7.1.3 Go Moudle

Go Moudle是依赖管理系统,解决了以前依赖管理系统存在的无法依赖同一个库的不同版本的问题。

通过go.mod 文件管理依赖包版本;通过go get 或者go mod指令工具管理依赖包

终极目标就是dinginess版本规则和管理项目依赖关系。