我是靠谱客的博主 孤独野狼,最近开发中收集的这篇文章主要介绍Go语言并发编程及依赖管理,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

目录

    • ????并发编程
      • Goroutine
      • CSP(Communicating Sequential Processes)
    • ????依赖管理
      • 依赖演变
      • 依赖管理三要素

????这里是CS大白话专场,让枯燥的学习变得有趣!
????没有对象不要怕,我们new一个出来,每天对ta说不尽情话!
????好记性不如烂键盘,自己总结不如收藏别人!

????并发编程

Goroutine

???? 为什么 Go 语言运行效率如此之高呢?因为它可以充分发挥多核优势,这里有个重要的概念:Goroutine(协程),Go 语言是利用协程达到高并发效果的。

image.png

  • 进程:程序一次动态执行的过程,操作系统资源分配最小单位,有独立内存空间。
  • 线程:轻量级进程,CPU调度的最小单位,多个线程共享所属进程的资源,MB级别。
  • 协程:轻量级线程,由用户控制调度,KB级别。

???? 如何开启一个协程呢?我们只需要在调用的函数前面添加 go 关键字,就能使这个函数以协程的方式运行。

package main
import (
"fmt"
"time"
)
func hello(i int) {
println("hello goroutine:" + fmt.Sprint(i))
}
func main() {
for i := 0; i < 5; i++ {
go hello(i) // go + 调用函数名(参数)
}
time.Sleep(time.Second) // 阻塞,保证子协程执行完之前主线程不退出
}

可以看到打印结果是并发乱序输出的:

image.png

CSP(Communicating Sequential Processes)

???? Go 语言中协程之间的通信提倡通过通信共享内存,而非通过共享内存实现通信,二者都支持实现,但是听起来很容易混淆,通过画图可以直观演示:

image.png

???? 通过通信共享内存:通过通道给目标协程发送信息。Channel(通道)分为无缓冲通道和有缓冲通道,构造方式:

  • 无缓冲通道:make(chan int)
  • 有缓冲通道:make(chan int,2)

image.png

如下代码所示,构造生产者A和消费者B可以实现有缓冲通道的协程通信,生产者产生数字,消费者计算数字的平方,并顺序打印。

package main
func CalSquare() {
src := make(chan int)
// 生产者
dest := make(chan int, 3) // 消费者,带缓冲区
go func() {
// A协程发送0~9至src中
defer close(src) // defer表示延迟到函数结束时执行,用于释放已分配的资源
for i := 0; i < 10; i++ {
src <- i
}
}()
go func() { // B协程计算src中数据的平方,并发送至dest中
defer close(dest)
for i := range src {
dest <- i * i
}
}()
for i := range dest {
println(i)
}
}
func main() {
CalSquare()
}

???? 通过共享内存通信:需要通过 sync 包中的 lock 进行加锁,如果不加锁会出现问题。如下代码开启5个协程,每个协程进行2000次+1操作,正确结果应该是10000,不加锁的结果不准确。项目中应避免对共享内存进行非并发安全的读写操作。

package main
import (
"sync"
"time"
)
var (
x
int64
lock sync.Mutex
)
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock() // 加锁
x += 1
lock.Unlock() // 解锁
}
}
func addWithoutLock() { // 不使用锁
for i := 0; i < 2000; i++ {
x += 1
}
}
func Add() {
x = 0
for i := 0; i < 5; i++ {
go addWithoutLock()
}
time.Sleep(time.Second)
println("WithoutLock x =", x)
x = 0
for i := 0; i < 5; i++ {
go addWithLock()
}
time.Sleep(time.Second)
println("WithLock x =", x)
}
func main() {
Add()
}

image.png

???? sync 包中的 WaitGruop 也可以实现并发任务同步,Add(delta int) 开启 delta 个协程,Done() 结束协程,Wait() 阻塞主线程。

image.png

????依赖管理

依赖演变

???? Go 依赖主要经历了 GOPATH -> Go Vendor -> Go Module 演变,现在主要采用 Go Module 方式。不同环境(项目)依赖的版本不同,需要控制依赖库的版本。

  • GOPATH:环境变量,Go 语言的工作区,通过 go get 下载最新版本的包到 src 目录下,项目代码直接依赖 src 下的所有源代码。但是有一个弊端就是无法实现package的多版本控制,A、B 项目可能依赖同一个包的不同版本。
  • Go Vendor:在项目目录下新增 vendor 文件夹,所有依赖包副本形式放在其中,通过 vendor => GOPATH 的方式寻址。但是还有问题就是更新项目时可能出现依赖冲突,依然无法控制依赖的版本。
  • Go Module:通过 go.mod 文件管理依赖包版本,通过 go get/go mod 指令工具管理依赖包。既能定义版本规则,又能管理项目依赖关系。

依赖管理三要素

???? 配置依赖 go.mode

module example/project/app //依赖管理基本单元
go 1.16
//原生库版本
require (
//单元依赖标识语法:[Module Path][Version/Pseudo-version]
example/lib1 v1.0.2
example/lib2 v1.0.0 // indirect
example/lib3 v0.1.0-20190725025543-5a5fe074e612
example/lib4 v0.0.0-20180306012644-bacd9c7ef1dd // indirect
example/lib5/v3 v3.0.2
// 主版本2+的模块会在路径后增加/vN后缀
example/lib6 v3.2.0+incompatible
// 对于没有go.mod文件且主版本2+的依赖+incompatible
)

go.mode 定义了自己的版本规则,分为语义版本和基于commit伪版本。

  • 语义版本:${MAJOR}.${MINOR}.${PATCH}

    v版本.新增功能.修复bug,不同版本不兼容,功能向下兼容。

  • 基于commit伪版本:${vx.0.0-yyyymmddhhmmss-abcdefgh1234}

    v版本-时间戳-校验码。

???? 中心仓库管理依赖库 Proxy

对于 go.mod 中定义的依赖,可以从对应仓库(Github)中下载指定软件依赖,从而完成依赖分发,但是会产生如下问题:

  • 无法保证构建确定性:软件作者直接修改软件版本,导致下次构建使用其他版本的依赖,或者找不到依赖版本。
  • 无法保证依赖可用性:软件作者删除软件,导致依赖不可用。
  • 增加第三方压力:代码托管平台负载过大。

可以使用 Go Proxy 解决上述问题,它会缓存源站中的软件内容,缓存的软件版本不会改变,并且在源站软件删除之后依然可用,构建时会直接从 Go Proxy 站点拉取依赖。Go Modules通过 GOPROXY 环境变量控制依赖寻址顺序:如 proxy1 -> proxy2 -> 源站

GOPROXY="https://proxy1.cn, https://proxy2.cn,direct"

???? 本地工具 go get/mod

image.png
image.png

最后

以上就是孤独野狼为你收集整理的Go语言并发编程及依赖管理的全部内容,希望文章能够帮你解决Go语言并发编程及依赖管理所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(33)

评论列表共有 0 条评论

立即
投稿
返回
顶部