go语言进阶 | 青训营笔记

67 阅读5分钟

go语言进阶 | 青训营笔记

这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天

1.线程和进程和协程概念

进程

一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程。

image.png

线程

进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。 与进程不同的是同类的多个线程共享进程的方法区资源,但每个线程有自己的程序计数器虚拟机栈本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

协程

一个线程中可以有任意多个协程,但某一时刻只能有一个协程在运行,多个协程分享该线程分配到的计算机资源。 与传统的系统级线程和进程相比,协程最大的优势在于“轻量级”。可以轻松创建上万个而不会导致系统资源衰竭。而线程和进程通常很难超过1万个。这也是协程别称“轻量级线程”的原因。

2.go并发

Go 在语言级别支持协程,叫goroutine,goroutine是Go语言并行设计的核心,有人称之为go程。 Goroutine从量级上看很像协程,它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。

一般golang语言中创建协程,只需在函数调⽤语句前添加 go 关键字,就可创建并发执⾏单元。开发⼈员无需了解任何执⾏细节,调度器会自动将其安排到合适的系统线程上执行。main函数为此go程序的主协程,其他创建的协程为从协程,当主协程退出后,从协程也自动销毁。

在并发编程中,我们通常想将一个过程切分成几块,然后让每个goroutine各自负责一块工作,当一个程序启动时,主函数在一个单独的goroutine中运行,我们叫它main goroutine。新的goroutine会用go语句来创建。而go语言的并发设计,让我们很轻松就可以达成这一目的。

package main

import (
	"fmt"
	"time"
)

// 从goroutine
func newTask() {
	i := 0
	for {
		i++
		fmt.Printf("new Goroutine:i=%d\n", i)
		time.Sleep(1 * time.Second)
	}
}

// 主goroutine
func main() {
	//创建一个go协程,去执行newTask()流程
	go newTask()
	//fmt.Println("main groutine exit")
	i := 0
	for {
		i++
		fmt.Printf("main goroutine:i=%d\n", i)
		time.Sleep(1 * time.Second)
	}
}

3.协程通信channel

channel是一个数据类型,主要用来解决go程的同步问题以及go程之间数据共享(数据传递)的问题。可以把它看成管道。并发核心单元通过它就可以发送或者接收数据进行通讯,这在一定程度上又进一步降低了编程的难度。引⽤类型 channel可用于多个 goroutine 通讯。其内部实现了同步,确保并发安全。

  • 有缓冲channel

在第 1 步,右侧的 goroutine 正在从通道接收⼀个值

在第 2 步,右侧的这个 goroutine独⽴完成了接收值的动作,⽽左侧的 goroutine 正在发送⼀个新值到通道⾥

在第 3 步,左侧的goroutine 还在向通道发送新值,⽽右侧的 goroutine 正在 从通道接收另外⼀个值。这个步骤⾥的两个操作既不是同步的,也不会互相阻塞

最后,在第 4 步,所有的发送和接收都完成,⽽通道⾥还有⼏个值,也有⼀些空 间可以存更多的值

  • 无缓冲channel

在第 1 步,两个 goroutine 都到达通道,但哪个都没有开始执⾏发送或者接收。

在第 2 步,左侧的 goroutine 将它的⼿伸进了通道,这模拟了向通道发送数据的⾏ 为。这时,这个 goroutine 会在通道中被锁住,直到交换完成。

在第 3 步,右侧的 goroutine 将它的⼿放⼊通道,这模拟了从通道⾥接收数据。这 个 goroutine ⼀样也会在通道中被锁住,直到交换完成。

在第 4 步和第 5 步,进⾏交换,并最终,在第 6 步,两个 goroutine 都将它们的 ⼿从通道⾥拿出来,这模拟了被锁住的 goroutine 得到释放。两个 goroutine 现在 都可以去做其他事情了。

func main() {
	c := make(chan int, 3) //带有缓冲的channel
	fmt.Println("len(c)=", len(c), "cap(c)", cap(c))
	go func() {
		defer fmt.Println("子go程结束")
		for i := 0; i < 3; i++ {
			c <- i
			fmt.Println("子go程正在运行,len(c)=", len(c), "发送的元素为=", i, "容量cap(c)=", cap(c))
		}
	}()
	time.Sleep(3 * time.Second)
	for i := 0; i < 3; i++ {
		num := <-c //从c中接受数据,并赋值给num
		fmt.Println("num=", num)
	}
	fmt.Println("main结束")
	//当channel已经满,再向里面写数据,就会阻塞
	//当channel为空,从里面取数据也会阻塞

}

func main() {
	//定义一个channel
	c := make(chan int)
	fmt.Println("")
	go func() {
		defer fmt.Println("goroutine结束")
		fmt.Println("gotoutine 正在运行...")
		c <- 666 //将666发送给c
	}()
	num := <-c //从c中接受数据,并赋值给num
	fmt.Println("num=", num)
	fmt.Println("main goroutine 结束...")
}