ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【无标题】

2022-01-22 17:30:56  阅读:126  来源: 互联网

标签:goroutine 调度 无标题 线程 Go channel 通道


Go语言中的并发编程

并发

goroutine实现,类似线程,++属于用户态的线程++,可以并发工作
goroutine是由Go语言的运行时(runtime)调度完成

线程是由操作系统调度完成。

Go语言还提供channel在多个goroutine间进行通信

goroutine和channel是 Go 语言秉承的 CSP(Communicating Sequential Process)并发模式的重要实现基础。

goroutine

程序员只需要定义很多个任务,让系统去帮助我们把这些任务分配到CPU上实现并发执行呢

Go程序会智能地将goroutine中的任务合理地分配给每个CPU。Go语言之所以被称为现代化的编程语言,就是因为它在语言层面已经内置了调度和上下文切换的机制

当你需要让某个任务并发执行的时候,你只需要把这个任务包装成一个函数,开启一个goroutine去执行这个函数就可以了,就是这么简单粗暴。

使用goroutine

Go语言中使用goroutine非常简单,只需要在调用函数的时候在前面加上go关键字,就可以为一个函数创建一个goroutine

P与M一般也是一一对应的。他们关系是:

P管理着一组G挂载在M上运行。当一个G长久阻塞在一个M上时,runtime会新建一个M,阻塞G所在的P会把其他的G 挂载在新建的M上。当旧的G阻塞完成或者认为其已经死掉时 回收旧的M。

一个goroutine必定对应一个函数,可以创建多个goroutine去执行相同的函数。

goroutine是并发执行的,而goroutine的调度是随机的


调度与栈

可增长的栈

OS线程(操作系统线程)一般都有固定的栈内存(通常为2MB),一个goroutine的栈在其生命周期开始时只有很小的栈(典型情况下2KB),goroutine的栈不是固定的,他可以按需增大和缩小,goroutine的栈大小限制可以达到1GB,虽然极少会用到这么大。所以在Go语言中一次创建十万左右的goroutine也是可以的

goroutine调度

GPM是Go语言运行时(runtime)层面的实现,是go语言自己实现的一套调度系统

单从线程调度讲,Go语言相比起其他语言的优势在于OS线程是由OS内核来调度的,goroutine则是由Go运行时(runtime)自己的调度器调度的,这个调度器使用一个称为m:n调度的技术(复用/调度m个goroutine到n个OS线程)
其一大特点是goroutine的调度是在用户态下完成的, 不涉及内核态与用户态之间的频繁切换,包括内存的分配与释放,都是在用户态维护着一块大的内存池, 不直接调用系统的malloc函数(除非内存池需要改变),成本比调度OS线程低很多。 另一方面充分利用了多核的硬件资源,近似的把若干goroutine均分在物理线程上, 再加上本身goroutine的超轻量,以上种种保证了go调度方面的性能。

Go语言中的操作系统线程和goroutine的关系:

  • 一个操作系统线程对应用户态多个goroutine。
  • go程序可以同时使用多个操作系统线程。
  • goroutine和OS线程是多对多的关系,即m:n。

channel

实现函数与函数之间的交换数据

Go语言的并发模型是CSP(CommunicatingSequentialProcesses),提倡通过通信共享内存而不是通过共享内存而实现通信

如果说goroutine是Go程序并发的执行体,channel就是它们之间的连接。

channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制

Go 语言中的通道(channel)是一种特殊的类型。通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。每一个通道都是一个具体类型的导管,也就是声明channel的时候需要为其指定元素类型。

channel类型

channel是一种类型,一种引用类型。

==### ==无缓冲通道阻塞问题:

无缓冲通道上的发送会阻塞,直到另一个goroutine在该通道上执行接收操作,这时值才能够发送成功。相反,如果有先有接受操作,那么接受端的goroutine也会阻塞,直到有发送端向通道传送数据才会唤醒接受端。

就如同没有设置代收点的快递,快递员送快递,而你不在,快递员就得等,
相反,他还没送到,你就得等。

使用无缓冲通道进行通信将导致发送和接收的goroutine同步化。因此,无缓冲通道也被称为同步通道

两种方式判断接收值的时候通道是否关闭:

  • for range遍历通道,当通道被关闭的时候就会退出for range
  • for i:OK := <- ch // i, ok := <-ch1 // 通道关闭后再取值ok=false
i, ok := <-ch1 // 通道关闭后再取值ok=false
			if !ok {
				break
			}

单向通道

有的时候我们会将通道作为参数在多个任务函数间传递,很多时候我们在不同的任务函数中使用通道都会对其进行限制,比如限制通道在函数中只能发送或只能接收。

  • chan<- int是一个只写单向通道(只能对其写入int类型值),可以对其执行发送操作但是不能执行接收操作;
  • <-chan int是一个只读单向通道(只能从其读取int类型值),可以对其执行接收操作但是不能执行发送操作。
func counter(out chan<- int) { //参数为  写单向通道(只能对其写入int类型值),可以对其执行发送操作但是不能执行接收操作
	for i := 0; i < 100; i++ {
		out <- i
	}
	close(out)
}
func squarer(out chan<- int, in <-chan int) { //参数为  写单向通道(只能对其写入int类型值),可以对其执行发送操作但是不能执行接收操作
	for i := range in {
		out <- i * i
	}
	close(out)
}
func printer(in <-chan int) {
	for i := range in {
		fmt.Println(i)
	}

}
func singalFuncCh() {
	ch1 := make(chan int)
	ch2 := make(chan int)
	go counter(ch1)      //ch1 只作为输出通道
	go squarer(ch2, ch1) // ch2 只作为输出通道 ch1 只作为输入通道
	printer(ch2)         // ch2 只作为输入通道
}

在函数传参及任何赋值操作中可以将双向通道转换为单向通道,但反过来是不可以的

通道总结

在这里插入图片描述


使用select语句能提高代码的可读性。

  • 可处理一个或多个channel的发送/接收操作。
  • 如果多个case同时满足,select会随机选择一个。
  • 对于没有case的select{}会一直等待,可用于阻塞main函数。

标签:goroutine,调度,无标题,线程,Go,channel,通道
来源: https://blog.csdn.net/weixin_45919793/article/details/122640533

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有