闲谈协程

进程

进程是应用程序的启动实例,进程拥有代码和打开的文件资源、数据资源、独立的内存空间。线程是最小的执行单元。

线程

线程从属于进程,是程序的实际执行者,一个进程至少包含一个主线程,也可以有更多的子线程,线程拥有自己的栈空间。进程是最小的资源管理单元。

协程

协程(Coroutines)是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。

协程和进程、线程不同,不是被操作系统内核所管理的,而是完全由程序所控制(在用户态执行)。因为不会像线程切换那样消耗资源,性能大幅度的提升。

协程是一个特殊的函数,这个函数可以在某个地方挂起,并且可以重新在挂起处外继续运行。协程与进程、线程相比并不是一个维度的概念。

一个进程可以包含多个线程,一个线程也可以包含多个协程。一个线程内可以由多个这样的特殊函数在运行,但是有一点必须明确的是,一个线程的多个协程的运行是串行的。如果是多核CPU,多个进程或一个进程内的多个线程是可以并行运行的,但是一个线程内协程却绝对是串行的,无论CPU有多少个核。当一个协程运行时,其它协程必须挂起。

协程和进程、线程之间的关系

  • 协程仅仅是一个特殊的函数,协程它进程和进程不是一个维度的。
  • 一个进程可以包含多个线程,一个线程可以包含多个协程。
  • 一个线程内的多个协程虽然可以切换,但是多个协程是串行执行的,只能在一个线程内运行,没法利用CPU多核能力。
  • 协程与进程、线程一样,切换是存在上下文切换问题的。进程、线程是根据操作系统自己的切换策略,用户是无感知的。协程的切换者是用户(编程者或应用程序),用户自己的程序所决定切换时机。
  • 进程的切换内容包括页全局目录、内核栈、硬件上下文,切换内容保存在内存中。进程切换过程是由“用户态到内核态到用户态”的方式,切换效率低。
  • 线程的切换内容包括内核栈和硬件上下文。线程切换内容保存在内核栈中。线程切换过程是由“用户态到内核态到用户态”, 切换效率中等。
  • 协程的切换内容是硬件上下文,切换内存保存在用户自己的变量(用户栈或堆)中。协程的切换过程只有用户态,即没有陷入内核态,因此切换效率高。

Golang

goroutine和协程区别

本质上,goroutine 就是协程。 

不同的是,Golang 在 runtime、系统调用等多方面对 goroutine 调度进行了封装和处理,当遇到长时间执行或者进行系统调用时,会主动把当前 goroutine 的CPU (P) 转让出去,让其他 goroutine 能被调度并执行,也就是 Golang 从语言层面支持了协程。

Golang 的一大特色就是从语言层面原生支持协程,在函数或者方法前面加 go关键字就可创建一个协程。

import (
	"fmt"
	"time"
)

func main(){
	for i:=0; i<10; i++ {
		go func(i int) {
			fmt.Printf("run in child coroutine %d.\n", i)
		}(i)
	}

	// 防止子协程还没有结束主协程就退出了
	time.Sleep(time.Second * 1)
}

通道

在GO里不同的并行协程之间交流的方式有两种,一种是通过共享变量,另一种是通过通道。Go 语言鼓励使用通道的形式来交流。

c := make(chan bool)
c <- true //写入
<- c //读取

非缓冲通道
无论是发送操作还是接收操作,一开始执行就会被阻塞,直到配对的操作也开始执行才会继续传递。由此可见,非缓冲通道是在用同步的方式传递数据。也就是说,只有收发双方对接上了,数据才会被传递。数据是直接从发送方复制到接收方的,中间并不会用非缓冲通道做中转。

缓冲通道
缓冲通道可以理解为消息队列,在有容量的时候,发送和接收是不会互相依赖的。用异步的方式传递数据。

c := make(chan int, value)

当 value = 0 时,通道是无缓冲阻塞读写的,等价于make(chan int);当value > 0 时,通道有缓冲、是非阻塞的,直到写满 value 个元素才阻塞写入。

关闭通道

读取一个已经关闭的通道会立即返回通道类型的零值,而写一个已经关闭的通道会抛异常。如果通道里的元素是整型的,读操作是不能通过返回值来确定通道是否关闭的。

通过for range语法读取,当通道为空时,循环会阻塞;当通道关闭,循环会停止。通过循环停止,我们可以认为通道已经关闭。

func main() {
	var c = make(chan int, 3)

	//子协程写
	go func() {
		c <- 1
		close(c)
	}()

	//直接读取通道,存在不知道子协程是否已关闭的情况
	//fmt.Println(<-c)
	//fmt.Println(<-c)

	//主协程读取:使用for...range安全的读取
	for value := range c {
		fmt.Println(value)
	}
}

 

你可能感兴趣的:(闲谈协程)