欢迎光临散文网 会员登陆 & 注册

Go语言goroutine、channel【重点】

2022-10-25 19:08 作者:苦茶今天断更了吗  | 我要投稿

Go语言goroutine、channel

进程、线程:

进程就是程序程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位。

线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单位。

③一个进程可以创建和销毁多个线程,同一个进程中的多个线程可以并发执行。

④一个程序至少有一个进程,一个进程至少有一个线程。

 

并发多线程程序在单核上运行

因为是在一个cpu上,比如有10个线程,每个线程执行10毫秒(进行轮询操作),从人的角度看,好像这10个线程都在运行,但是从微观上看,在某一个时间点看其实只有一个线程在执行

 

并行多线程程序在多核上运行

因为是在多个cpu上(比如有10个cpu),比如有10个线程,每个线程执行10毫秒(各自在不同cpu上执行),从人的角度看,这10个线程都在运行,但是从微观上看,在某一个时间点看,也同时有10个线程在执行

Go协程goroutine、Go主线程:

Go主线程(有程序员直接称为线程 / 也可以理解成进程):一个Go线程上,可以起多个协程goroutine,可以这样理解,协程是轻量级的线程【编译器做优化】。

Go协程goroutine的特点:

①有独立的栈空间

②共享程序堆空间

③调度由用户控制

④协程是轻量级的线程

Go协程goroutine的案例:

1.在主线程(可以理解成进程)中,开启一个goroutine,该协程每隔1秒输出"hello,world"。

2.在主线程中也每隔一秒输出"hello.golang",输出10次后,退出程序。

3.要求主线程和goroutine同时执行。

①如果主线程退出了,协程没执行完毕也会退出。

②主线程是一个物理线程,直接作用在cpu上的。是重量级的,非常耗费cpu资源。

③协程从主线程开启的,是轻量级的线程,是逻辑态。对资源消耗相对小。

④Golang的协程机制是重要的特点,可以轻松的开启上万个协程。其它编程语言的并发机制是一般基于线程的,开启过多的线程,资源耗费大,这里就突显Golang 在并发上的优势了。

 

Go协程goroutine的调度模型:

MPG:

M:操作系统的主线程(是物理线程)

P:协程执行需要的上下文

G:协程

MPG模式运行的状态1:

当前程序有三个M,如果都在一个cpu运行,就是并发,如果在不同的cpu运行,就是并行。

 

MPG模式运行的状态2:

分成两个部分来看;

M0主线程正在执行G0协程,另外有三个协程在队列等待。

如果G0协程阻塞,这时就会创建M1主线程(也可能是从已有的线程池中取出M1),并且将等待的3个协程挂到M1下开始执行,M0的主线程下的G0仍然执行。

这样的MPG调度模式,可以既让G0执行,同时也不会让队列的其它协程一直阻塞,仍然可以并发/并行执行。

等到G0不阻塞了,M0会被放到空闲的主线程继续执行(从已有的线程池中取),同时G0又会被唤醒。

 

设置Golang运行的cpu数:

为了充分利用多cpu的优势,在Golang程序中,设置运行的cpu数目。

go1.8后,默认让程序运行在多个核上,可以不用设置了。

go1.8前,还是要设置一下,可以更高效的利益cpu。

 

channel(管道)-看个需求

需求:

现在要计算 1-200 的各个数的阶乘,并且把各个数的阶乘放入到map中。

最后显示出来。要求使用goroutine完成

思路:

1. 编写一个函数,来计算各个数的阶乘,并放入到 map中。

2. 我们启动的协程多个,统计的将结果放入到 map中。

3. map 应该做出一个全局的。

4.因为没有对全局变量m加锁,因此会出现资源争夺问题,代码会出现错误,提示 concurrent map writes。解决方案:加入互斥锁。

 

不同goroutine之间如何通讯

①全局变量的互斥锁

②使用管道channel来解决

 


为什么需要channel

①前面使用全局变量加锁同步来解决goroutine的通讯,但不完美

②主线程在等待所有 goroutine全部完成的时间很难确定,这里设置10秒,仅仅是估算。

③如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有 goroutine 处于工作状态,这时也会随主线程的退出而销毁

通过全局变量加锁同步来实现通讯,也并不利于多个协程对全局变量的读写操作

⑤上面种种分析都在呼唤一个新的通讯机制-channe1

 

channel的基本介绍

Go语言中的通道(channel)是一种特殊的类型。

在任何时候,同时只能有一个goroutine访问通道进行发送和获取数据。

①channel本身是一个队列,先进先出

②线程安全,多goroutine访问时,不需要加锁

③本身是有类型的,string,int等,如果要存多种类型,则定义成interface类型

channel是引用类型,必须make后才能使用,一旦make,容量就确定了,不会增加!!

 


特点:

①一旦初始化容量,就不会改变了。

②当写满时,不可以写,取空时,不可以取,否则报dead lock。

③发送将持续阻塞直到数据被接收。

Go程序运行时能智能地发现一些永远无法发送成功的语句并做出提示。

④接收将持续阻塞直到发送方发送数据。

如果接收方接收时,通道中没有发送方发送数据,接收方也会发生阻塞,直到发送方发送数据为止。

⑤通道一次只能接收一个数据元素

 

定义/声明channel:var变量名chan数据类型

举例:

Var intChan chan int (intChan用于存放int数据)

Var mapChan chan map[int]string (mapChan用于存放map[int]string类型)

Var perChan chan Person

Var perChan2 chan *Person

说明:

channel是引用类型;

channel必须初始化才能写入数据,即make后才能使用管道是有类型的。

 

管道的初始化,写入数据到管道,从管道读取数据

fmt.Printf("intChan的值=%v  intChan本身的地址=%p\n",  intChan,  &intChan):

channel和指针一样,存放在一个内存单元中,有它的地址,它的值是一个int类型的地址。

 

读写channel案例演示:

①创建一个intChan,最多可以存放3个int,存数据到intChan,然后再取出这三个int。

②创建一个mapChan,最多可以存放10个map[string]string的key-val,演示写入和读取。

③创建一个catChan,最多可以存放10个cat结构体变量,演示写入和读取。

 ④创建一个catChan2,最多可以存放10个*Cat变量,演示写入和读取

 ⑤创建一个allChan,最多可以存放10个任意数据类型变量,演示写入和读取

 ⑥注意空接口类型的 channel:


定义interface类型的空接口,可以接收任意类型的数据,但是在取出来的时候,必须断言!a := newCat.(Cat)

 

channel的关闭:close( )

关闭之后,不能再写入,只能读。只能由发送者执行这句代码。

 

channel 的遍历for--range遍历,不用for循环

①在遍历时,如果channel没有关闭,则出现deadlock的错误

②在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完后,就会退出遍历。

应用

实例1:请完成goroutine和channel协同工作的案例,具体要求:

①开启一个writeData协程,向管道intChan中写入50个整数;

②开启一个readData协程,从管道intChan中读取writeData写入的数据;

③注意: writeData和readDate操作的是同一个管道;

④主线程需要等待writeData和readDate协程都完成工作才能退出管道。

方法:

①开两个管道;

②当writeData协程完成后,close数据管道,readData协程对数据管道intChan的数据读完之后,就向退出管道exitChan写入一个 true,close掉;

③主线程循环检测退出管道里是否有数据,如果有,说明readData协程完成,主程序就可以退出了。

实例2:要求统计1-200000的数字中,哪些是素数?

分析思路:使用并发/并行的方式,将统计素数的任务分配给多个(4个)goroutine去完成。

定义三个管道:

intChan :放80000个数

primeChan:放素数

exitChan :4个协程运行完毕的标志

 

 

 channel使用细节和注意事项

①channel可以声明为只读 / 只写性质

使select可以解决从管道取数据的阻塞问题不知道何时关闭管道时

③goroutine中使用recover,解决“协程中出现panic,导致程序崩溃”的问题。

 

 通道的数据接收 4种写法。

阻塞接收数据

将接收变量作为<-操作符的左值,格式:data := <-ch

执行该语句时将会阻塞,直到接收到数据并赋值给data变量。

 

②非阻塞接收数据

语句不会发生阻塞,格式:data, ok := <-ch

data:表示接收到的数据。未接收到数据时,data为通道类型的零值。

ok:表示是否接收到数据。

非阻塞的通道接收方法可能造成高的CPU占用,因此使用非常少。如果需要实现接收超时检测,可以配合 select 和计时器 channel进行。

 

③接收任意数据,忽略接收的数据

阻塞接收数据后,忽略从通道返回的数据,格式:<-ch

执行该语句时将会发生阻塞,直到接收到数据,但接收到的数据会被忽略。

这个方式实际上只是通过通道在 goroutine 间阻塞收发实现并发同步

 

使用通道做并发同步的写法,可以参考下面的例子:


④循环接收

通道的数据接收可以借用for range语句进行多个元素的接收操作,格式:

for data := range ch {

}

通道ch是可以进行遍历的,遍历的结果就是接收到的数据。数据类型就是通道的数据类型。通过for遍历获得的变量只有一个,即上面例子中的data。

 

 


Go语言goroutine、channel【重点】的评论 (共 条)

分享到微博请遵守国家法律