Golang语言中的goroutine和channel

1. goroutine的基本概念介绍

1.1 引入

进程与线程的说明:进程与线程
举例(这是我见过最好理解的例子了):百度网盘的启动之后,百度网盘软件就是一个进程。百度网盘下载文件时,可以同时下载好几个文件,此时每一个文件的下载过程就是线程。
Golang语言中的goroutine和channel_第1张图片
并发与并行的区别:
1)多线程程序在单核上运行,就是并发。(同一时刻,只有一个任务在执行)
2)多线程程序在多核上运行,就是并行。
Golang语言中的goroutine和channel_第2张图片

1.2 Go协程和Go主线程

协程是轻量级的线程。goroutinue可以实现并行,也就是说,多个协程可以在多个处理器同时跑。而协程同一时刻只能在一个处理器上跑(把宿主语言想象成单线程的就好了)。

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此,协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作执行者则是用户自身程序,goroutine也是协程。

Golang语言中的goroutine和channel_第3张图片
Go协程的特点:
1)有独立的栈空间
2)共享程序堆空间
3)调度由用户控制
4)协程是轻量级的线程

1.3 案例学习

Golang语言中的goroutine和channel_第4张图片
Golang语言中的goroutine和channel_第5张图片
针对以上程序,在运行过程中的流程如下图所示:
Golang语言中的goroutine和channel_第6张图片

1.4 goroutine的调度模型

Golang语言中的goroutine和channel_第7张图片

Go的调度器内部有四个重要的结构:M,P,S,Sched,如上图所示(Sched未给出)

  • M:M代表内核级线程,一个M就是一个线程,goroutine就是跑在M之上的;M是一个很大的结构,里面维护小对象内存cache(mcache)、当前执行的goroutine、随机数发生器等等非常多的信息
  • G:代表一个goroutine,它有自己的栈,instruction pointer和其他信息(正在等待的channel等等),用于调度。
  • P:P全称是Processor,处理器,它的主要用途就是用来执行goroutine的,所以它也维护了一个goroutine队列,里面存储了所有需要它来执行的goroutine
  • Sched:代表调度器,它维护有存储M和G的队列以及调度器的一些状态信息等。

1.5 golang设置CPU运行数目

Golang语言中的goroutine和channel_第8张图片

2. channel的使用

channel是goroutinues之间进行通信的利器。channel可以形象比喻为工厂里的传送带,一头的生产者goroutine往传输带放东西,另一头的消费者goroutinue则从输送带取东西。channel实际上是一个有类型的消息队列,遵循先进先出的特点。

2.1 channel的基本介绍

1)channel类似于一个队列。
2)channel中的数据是先进先出FIFO
3)线程安全。多goroutine访问时,不需要加锁操作。
4)channel是有数据类型的。一个string的channel 只能存放string 类型数据。
Golang语言中的goroutine和channel_第9张图片

2.2 channel的初步使用

  • 定义与声明

var 变量名 chan 数据类型
channel变量必须初始化后才可以使用,即就是make。

  • 初始化

var intChan chan int
intChan = make( chan int,3 )

  • 操作符号

intChan <- ele 表示ele被发送给channel intChan ;
ele2 <- intChan 表示从channel intChan 取一个值,然后赋给ele2

  • 注意事项
    1)channel中只能存放指定类型的数据
    2)channel的数据放满之后,就不能再放了,在初始化的时候确定了channel的容量
    3)在没有使用协程的情况下,如果channel数据取完了,再取数据,程序会报错。
    Golang语言中的goroutine和channel_第10张图片
  • 管道的关闭和遍历
  • 内建函数 close(intChan) //这时不能在往channel里面写入数据
    遍历使用for-range方式:遍历时未关闭管道,出现deadlock错误;遍历时关闭管道,正常遍历结束。

2.3 channel的阻塞机制

writeData协程 :往管道中写入数据,总共要写50个,管道的容量是10;
readData协程:关闭读协程;
此时,管道出现阻塞,程序报deadlock错误。因为管道已满,没有被读取,无法再继续写入数据。
Golang语言中的goroutine和channel_第11张图片

3 goroutine和channel的应用实例

3.1 要求统计1-80000的数字中,哪些是素数?

Golang语言中的goroutine和channel_第12张图片
Golang语言中的goroutine和channel_第13张图片
Golang语言中的goroutine和channel_第14张图片
Golang语言中的goroutine和channel_第15张图片
效果演示
Golang语言中的goroutine和channel_第16张图片

Golang语言中的goroutine和channel_第17张图片

package main

import(
	"fmt"
)

var (
	numChan = make(chan int, 2000)
	resChan = make(chan map[int]int,2000)
	exitChan = make(chan bool, 8)
)

func write(){
	for i:=1;i<=2000;i++{
		numChan<- i
	}
	close(numChan)
}

func calc(){
	for{
		v,ok := <- numChan
		if !ok{
			break
		}
		num := 0
		for i:=1;i<=v;i++{
			num += i
		}
		res := make(map[int]int)
		res[v] = num
		resChan<- res
		
	}
	fmt.Println("一个协程取不到数据")
	exitChan<- true
}

func main(){
	go write()
	for i:=0;i<8;i++{
		go calc()
	}

	go func(){
		for i:=0;i<8;i++{
			<- exitChan
		}
		close(resChan)
		close(exitChan)
	}()

	for{
		res , ok := <-resChan
		if !ok{
			break
		}
		for index, sum := range res{
			fmt.Printf("得到的计算结果是res[%d]=%d\n",index,sum)
		}
		
	}
}

3.2 生产与消费者应用:

https://blog.csdn.net/littleschemer/article/details/70232659

4. 补充

1)channel可以定义为只读,或者只写的。默认情况是可读可写的。
Golang语言中的goroutine和channel_第18张图片
2)使用select可以解决从管道取数据的阻塞问题。
Golang语言中的goroutine和channel_第19张图片
3)goroutine中使用recover,解决协程中出现panic。
Golang语言中的goroutine和channel_第20张图片

可参考博客:
https://www.cnblogs.com/wdliu/p/9272220.html

你可能感兴趣的:(Golang)