📅 2024年5月12日

📦 使用版本为 1.21.5

ps:建议先学习操作系统,学完进程后再来看

协程

⭐️ coroutine协程,也叫轻量级线程,或者是用户态的线程,不受操作系统直接调度

⭐️ 协程是轻量的,比线程更轻,占用的内存更少

⭐️ 一个线程中可以有任意多个协程,但某一时刻只能有一个协程在运行,多个协程分享该线程分配到的计算机资源 (操作系统知识)

⭐️ 很多语法都不直接支持协程,需要通过调用库的方式,而 Go语言本来它就支持协程

⭐️ 在协程中,调用一个任务就像调用一个函数一样,消耗资源也少,可以达到进程、线程并发相同的效果

🌟 go并发

⭐️ Go 在语言级别支持协程,叫 goroutineGo 语言标准库提供的所有系统调用操作(包括所有同步 IO操作),都会出让 CPU给其他 goroutine。这让轻量级线程的切换管理不依赖于系统的线程和进程,也不需要依赖于 CPU的核心数量

⭐️ Go语言为并发编程而内置的上层API基于顺序通信进程模型 CSP(communicating sequential processes)。这就意味着显式锁都是可以避免的,因为Go通过相对安全的通道发送和接受数据以实现同步,这大大地简化了并发程序的编写

⭐️ Go语言中的并发程序主要使用两种手段来实现。goroutinechannel

🌟 Goroutine

⭐️ goroutineGo语言并行设计的核心,有人称之为 go程。 Goroutine从量级上看很像协程,它比线程更小,十几个 goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些 goroutine之间的内存共享

⭐️ 执行 goroutine只需极少的栈内存(大概是4~5KB),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。goroutinethread(Java)更易用、更高效、更轻便

1️⃣ 创建协程

⭐️ 使用 go关键字创建协程,go关键字后面必须是一个函数调用,下面有三种创建协程的方法,都是输出,但是协程的输出语句并不会被执行,因为启动完这三个协程主线程也就退出了,主线程退出,线程也就退出了

func main() {
	go test()
	go func() {
		fmt.Println("我是协程2")
	}()
	go fmt.Println("我是协程3")
}

func test() {
	fmt.Printf("我是协程1")

}

⭐️ 可以加工 sleep等待一些协程运行,但是协程的执行顺序也是不确定的,可以多次执行下面的代码

func main() {
	go test()
	go func() {
		fmt.Println("我是协程2")
	}()
	go fmt.Println("我是协程3")
	//暂停1ms
	time.Sleep(time.Millisecond)
}

func test() {
	fmt.Printf("我是协程1\n")

}

//输出: 会发现每次执行顺序完全不一样
PS D:\learnCode\Go\gosql> go run .
我是协程3
我是协程1
我是协程2
PS D:\learnCode\Go\gosql> go run .
我是协程1
我是协程3
我是协程2

⭐️ 上面列子使用 time.Sleep都是已经知道代码可以在多少时间内执行完毕,那如果不确定代码多久执行,就可以使用 Go提供的其他方法

  • channel: 管道
  • WaitGroup:信号量
  • Context:上下文

三种方法有着不同的适用情况,WaitGroup可以动态的控制一组指定数量的协程,Context更适合子孙协程嵌套层级更深的情况,管道更适合协程间通信。对于较为传统的锁控制,Go也对此提供了支持:

  • Mutex:互斥锁
  • RWMutex :读写互斥锁

⭐️ 经典消费者问题又要来咯