Go语言入门-工程实践 | 青训营笔记

93 阅读2分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记。

一、Go语言进阶

1. 并发和并行:

并发是同一时间段内执行多个任务。并行是同一时刻执行多个任务。

从多线程程序运行的视角来看:并发是多线程程序在一个核的CPU上运行,并行是多线程程序在多个核的CPU上运行。
并行可以理解为实现并发的一个手段。

Go语言实现了并发性能极高的一个调度模型,通过高效的调度,可以最大限度的利用计算机资源,充分发挥多核计算机的优势。

2. Goroutine

协程:用户态,轻量级线程,栈KB级别。
线程:内核态,线程跑多个协程,栈MB级别。

go语言一次可以创建上万左右的协程。

操作系统的栈内存通常为2mb,go语言的goroutine一般为2kb。
  • Go语言开启协程只需要在调用的函数前面加上一个关键字:go。
func hello(i int) {
	println("hello goroutine : " + fmt.Sprint(i))
}

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

3. CSP (Communicating Sequential Processes)

image.png

Go语言提倡通过通信共享内存而不是通过共享内存而实现通信。

通过通信共享内存:Gorountine是go程序并发的一个执行体,通道相当于把协程做了一个连接 ,就像是一个传输队列,遵循先入先出,能保证收发数据的顺序。
通过channel(通道)可以让一个Gorountine发送特定的值到另一个Gorountine的通信机制。go保留了通过共享内存实现通信的机制。

通过共享内存实现通信: 必须通过一个互斥量对内存进行一个加锁,也就是需要获取临界区的一个权限。
这样,不同的Gorountine之间容易发生数据竞态的一个问题。一定程度上,影响程序的一个性能。

4. Channel

make(chan 元素类型,[缓冲大小])
- 无缓冲通道:makechan int)
- 有缓冲通道:makechan int, 2

image.png

func CalSquare() {
	src := make(chan int)
	dest := make(chan int, 3)
        
	go func() {
               //子协程发送0~9数字
		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)
	}
}

5. 并发安全Lock

通过Lock加锁、Unlock解锁。

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 = 0
   for i := 0; i < 5; i++ {
   	go addWithLock()
   }
   time.Sleep(time.Second)
   println("WithLock:", x)
}

image.png

7. WaitGroup

go语言可以通过使用WaitGroup实现并发任务的一个同步。
暴露了三个方法:wait(),Add(delta int),Done。内部就是维护了一个计数器。
计数器的值可以增加或者减少。增加一个任务,用add增加1,每个任务完成,用done方法为计数器减1。
最后调用wait方法阻塞,等待所有的并发任务执行完。计数器为0的时候,所有的并发任务都已经完成。

image.png

func ManyGoWait() {
	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()
}

image.png

二、依赖管理

  • 工程项目不可能基于标准库0~1编码搭建
  • 管理依赖库

1. Go依赖管理演进

GOPATH —> Go Vendor -> Go module

GOPATH:存在弊端:项目A和项目B依赖于某一package的不同版本,无法实现package的多版本控制。
 
Go Vendor:项目目录下增加vendor文件,所有依赖包副本形式存放在项目vendor文件下。
通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题。
弊端如下:无法控制依赖版本、更新项目又可能出现依赖冲突,导致编译出错。

Go Moudle:通过go.mod文件管理依赖版本。通过go get/go mod指令工具管理依赖版本。

2. 依赖管理三要素

配置文件,描述依赖:go mod。

中心仓库管理依赖:proxy。

本地工具:go get/mod

三、总结

通过本次课程,我了解到了Go语言并发相关的知识,并且明白了Go语言高性能的原理。其次了解了Go语言依赖管理的演进路线。在本次课程中,Go语言的协程的概念给我留下了深刻的印象,它是一种更加轻量级的线程,并且执行异步方法也更加的简单。