Go语言进阶 | 青训营笔记

97 阅读4分钟

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

一.本堂课重点内容

  • Go语言进阶

二.详细知识点介绍

Go语言进阶

  • 并发 VS 并行

并发指多线程程序在一个核的cpu上运行

image.png

并行指多线程程序在多个核的cpu上运行

image (1).png

  • Goroutine

Goroutine也叫 Go 程,是由 Go 运行时管理的轻量级线程

线程 : 用户态,轻量级线程,栈MB级别

协程 : 内核态,线程跑多个协程,栈KB级别

image (2).png

可以用go方法名启动一个新线程并执行

go f(x, y, z)
f(x, y, z)

fxy 和 z 的求值发生在当前的 Go 程中,而 f 的执行发生在新的 Go 程中

Go 程在相同的地址空间中运行,因此在访问共享的内存时必须进行同步。

  • CSP(Communicating Sequential Processes)

CSP中文名称是通信顺序进程,Go语言中提倡通过通信共享内存

image (3).png

  • Channel

Channel在Go的官方文档中解释为信道,它分为无缓冲通道和有缓冲通道两种

image (4).png

信道是带有类型的管道,你可以通过它用信道操作符 <- 来发送或者接收值。

ch <- v    // 将 v 发送至信道 ch。
v := <-ch  // 从 ch 接收值并赋予 v。

(“箭头”就是数据流的方向。)

和映射与切片一样,信道在使用前必须创建(无缓冲通道创建方法):

ch := make(chan int)

默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得 Go 程可以在没有显式的锁或竞态变量的情况下进行同步。

以下示例对切片中的数进行求和,将任务分配给两个 Go 程。一旦两个 Go 程完成了它们的计算,它就能算出最终的结果。

package main

import "fmt"

func sum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	c <- sum // 将和送入 c
}

func main() {
	s := []int{7, 2, 8, -9, 4, 0}

	c := make(chan int)
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)
	x, y := <-c, <-c // 从 c 中接收

	fmt.Println(x, y, x+y)
}
-5 17 12

信道可以是带缓冲的。将缓冲长度作为第二个参数提供给 make 来初始化一个带缓冲的信道:

ch := make(chan int, 100)

仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞。

  • 并发安全Lock

我们已经看到信道非常适合在各个 Go 程间进行通信。

但是如果我们并不需要通信呢?比如说,若我们只是想保证每次只有一个 Go 程能够访问一个共享的变量,从而避免冲突?

这里涉及的概念叫做 互斥(mutualexclusion),我们通常使用 互斥锁(Mutex) 这一数据结构来提供这种机制。

Go 标准库中提供了 sync.Mutex 互斥锁类型及其两个方法:

  • Lock
  • Unlock

我们可以通过在代码前调用 Lock 方法,在代码后调用 Unlock 方法来保证一段代码的互斥执行。参见 Inc 方法。

我们也可以用 defer 语句来保证互斥锁一定会被解锁。参见 Value 方法。

package main

import (
	"fmt"
	"sync"
	"time"
)

// SafeCounter 的并发使用是安全的。
type SafeCounter struct {
	v   map[string]int
	mux sync.Mutex
}

// Inc 增加给定 key 的计数器的值。
func (c *SafeCounter) Inc(key string) {
	c.mux.Lock()
	// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
	c.v[key]++
	c.mux.Unlock()
}

// Value 返回给定 key 的计数器的当前值。
func (c *SafeCounter) Value(key string) int {
	c.mux.Lock()
	// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
	defer c.mux.Unlock()
	return c.v[key]
}

func main() {
	c := SafeCounter{v: make(map[string]int)}
	for i := 0; i < 1000; i++ {
		go c.Inc("somekey")
	}

	time.Sleep(time.Second)
	fmt.Println(c.Value("somekey"))
}

  • WaitGroup

WaitGroup底层是一个计数器,通过实现Add方法向计数器中添加指定值。通过实现Done方法来实现计数器的-1操作。当协程开始并且达到计数器指定数目时,程序便会阻塞,也可通过Wait方法使程序进入无限额的等待,当计数器值减为0时,阻塞的程序便会释放,然后继续执行下去。

image (6).png

三.课后个人总结

在学习Go语言进阶课程得过程中,有许多东西需要我们自己反复去揣摩,更多的是理解它怎么用,为什么这么用,除了课程所教授的,这些东西还有什么别的特点,每次课后,我都会去查阅Golang的官方文档,看官方对Go语言基础或进阶给出的解释又是如何,学习就是不断攀登的过程,只有不懈努力,才会获得成功。

四.引用

  • 字节內部课 - Go语言入门
  • Golang官方文档