Go语言快速上手(三) | 青训营笔记

114 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第3篇笔记,非常感谢字节跳动无偿地分享技术知识并细致地讲解指导。本篇笔记主要介绍go语言的多线程知识

一、并发与并行

并发: 指一段时间内,系统能处理多个任务,不要求是同时执行;

并行: 指系统能同时处理多个任务,即同一个时刻就能处理多个任务。

显然,并行是并发的子集,需要有多个核的CPU,多个任务同时在不同的核上运行,是并发的一种实现方式。

并发的另一种实现方式是共享时间片,即把一段时间(如1秒)分成多个时间片(如10个100ms),然后就可以让多个任务分别在不同的时间片上运行,最终得到一段时间内处理多个任务的效果。

二、Go语言协程

Go routine是go语言本身自带的功能,能够在语言层面支持并发,以更高的效率来开发多线程程序。

Go routine被称为协程,能在用户态下完成全过程,不需要陷入操作系统的内核态。线程是一种轻量级进程,而协程就是轻量级线程。线程的栈是MB级别,协程的栈是KB级别,协程的开销更小;一个进程可以跑多个线程,一个线程可以跑多个协程,协程的并行粒度更小。因此,Go应用程序可以轻松地并发运行数千个Goroutines,并行性能大大提高。

开启协程的语法非常简单,只需要 “ go 函数名(参数列表) ” 即可,也就是在函数调用前面加上go关键字。下面的代码演示了同时开启五个协程,go后面是定义了一个无名函数并调用,调用时传入了参数i。

import (
	"fmt"
	"time"
)

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)
}

三、通道Channel

Go语言采用CSP(communicating sequential processes)并发模型,即“不要以共享内存的方式来通信,相反,要通过通信来共享内存”。

我们可以简单理解为,不需要多个进程同时运行去主动访问共享内存,而是通过通道(channel)以通信顺序来依次执行进程。

go语言中通道对应的关键字为chan,用法如下:

func CalSquare() {
	src := make(chan int)     //无缓冲通道
	dest := make(chan int, 3) //缓冲区大小为3个int值的通道
	go func() {               //产生[0,9]的int值传入通道src
		defer close(src) // 延迟执行语句,return之前会倒序执行所有的defer
		for i := 0; i < 10; i++ {
			src <- i
		}
	}()
	go func() { //从src通道中依次取出数据,并将其平方传入通道dest
		defer close(dest)
		for i := range src {
			dest <- i * i
		}
	}()
	for i := range dest { //从dest通道中依次取出数据并打印
		//复杂操作
		println(i)
	}
}

四、同步

Go语言的sync库中提供了两种同步方式,互斥锁Mutex等待组WaitGroup

互斥锁是传统的同步方式,有上锁和解锁操作,可以看作是值只能为0和1的信号量

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

等待组表示一个需要等待的go runtine集合,有Add、Done、Wait操作,可以分别看作是信号量的V操作、P操作、值为0操作

func ManyGoWait() {
	var wg sync.WaitGroup
	wg.Add(5) // 信号量+5
	for i := 0; i < 5; i++ {
		go func(j int) {
			defer wg.Done() // 信号量-1
			hello(j)
		}(i)
	}
	wg.Wait() //阻塞知道信号量为0
}