GO语言之Goroutine和channel

news2024/10/6 21:35:50

1,goroutine-看一个需求

需求:要求统计1-90000000000的数字中,哪些是素数哦?

分析思路:

1)传统的方法,就是使用一个循环,循环的判断各个数是不是素数。

2)使用并发或者并行的方式,将统计素数的任务分配给多个goroutine去完成,这时就会用到goroutine

2,goroutine-基本介绍

2.1进程和线程介绍

2.2程序,进程和线程的关系示意图

2.3并发和执行

并发和并行

1)多线程程序在单核上运行就是并发

2)多线程程序在多核上运行就是并行

3)图示:

并发:一个cpu同时执行多个线程

并行:多个cpu上执行多个线程,就相当于一个cpu执行一个线程

2.4GO协程核GO主线程

GO主线程(有程序员直接称为线程/也可以理解为进程):一个GO线程上,可以有多个协程,你可以理解为协程是轻量级的线程[编译器做的优化]

GO协程的特点

1)有独立的栈空间

2)共享程序堆空间

3)调度由用户控制

4)协程是轻量级的线程

3,goroutine-简单例子

3.1案例说明

请编写一个程序,完成如下功能

1)在主线程(可以理解成进程)中,开启一个goroutine,该协程每隔一秒输出一次helloworld“

2)在主线程中也每隔一秒输出”hellogolang“,输出10次后,退出程序

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

4)画出主线程和协程的执行流程图

主线程和协程执行流程图

3.2小结

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

2)协程从主线程开启,是轻量级的线程,是逻辑态。对资源的耗费相对较小

3)golang的协程机制是重要的特点,可以轻松的开启上万个协程。其他语言的并发机制是一般基于线程的,开启过多的线程耗费资源大,这里就凸显出golang的优势了

4,goroutine的调度模型

4.1MPG模式基本介绍

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

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

3)G:协程

4.2MPG模式运行的状态1

1)当前程序有三个M,如果三个M都在一个CPU运行,就是并发,如果在不同的CPU下运行就并行

2)M1,M2,M3正在执行一个G,M1的协程队列有三个,M2的协程队列有三个,M3的协程队列有两个

3)从上图可以看出:GO的协程是轻量级的线程,是逻辑态的,GO可以容易起上万个协程

4)其他程序c/java的多线程往往是内核态的,比较重量级,几千个线程可能耗光CPU

4.3MPG模式运行的状态2

1)分成两部分来看

2)原来的情况是M0主线程正在执行G0线程,另外有三个线程在队列等待

3)如果G0线程阻塞,比如读取文件或者数据库等

4)这时就会创建出m1主线程(也可能是从已经有的线程中取出M1)并且将等待的3个协程挂到M1下开始执行,M0的主线程下的G0仍然执行io的读写

5)这样的MPG调度模式,可以既让G0执行同时也可以让队列的其他的协程执行,仍然可以并发执行

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

5,设置Golang运行的CPU数

说明:为了充分利用多cpu的优势,在Goalng程序中,设置cpu数目

6,channel(管道)-需求

需求:现在要计算1-200各个数的阶乘,并且把各个数的阶乘放到map中。最后显示出来,要求使用goroutine来完成

思路:

1)使用goroutine来完成,效率高,但是会出现并发/并行安全问题

2)这里就提出了不同goroutine如何通信的问题

代码实现:

1)使用goroutine来完成(看看goroutine并发完成会出现什么问题)

2)在运行某个程序时,如何知道是否存在资源竞争问题。在编译程序时,增加一个参数-race即可

package Goroutine

import (
	"fmt"
	"time"
)

var(
	myMap=make(map[int]int,10)
)

//计算n!并放入到map里
func operation(n int)  {
	res:=1
	for i:=1;i<=n;i++{
		res*=i
	}
	myMap[n]=res
}

func Test3()  {
	//我们开启多个协程去完成这个任务

	for i:=1;i<=200;i++{
		go operation(i)
	}

	time.Sleep(time.Second*10)

	fmt.Println(myMap)
}

会有错误产生

6.1不同goroutine之间如何通讯

1)全局变量互斥锁

2)使用channel来解决

6.2使用全局变量加锁同步改进程序

因为没有对全局变量加锁,因此会出现资源争夺的问题,代码会出现错误,提示

解决方案:加入互斥锁

我们的数阶乘很大,将数改为uint64()

package Goroutine

import (
	"fmt"
	"sync"
	"time"
)

var(
	myMap2=make(map[int]uint64,10)
	//声明一个全局互斥锁,lock是一个全局互斥锁,sync是包:synchorized同步
	//Mutex:是互斥
	lock sync.Mutex
)
func operation2(n int)  {
	var res uint64=1
	for i:=1;i<=n;i++{
		res*=uint64(i)
	}
	//我们将res放入myMap
	//加锁
	lock.Lock()
	myMap2[n]= res
	//解锁
	lock.Unlock()
}

func Test4()  {
	for i:=1;i<=200;i++{
		go operation2(i)
	}

	time.Sleep(time.Second*10)
	//这里我们输出
	//加锁
	lock.Lock()
	fmt.Println(myMap2)
	lock.Unlock()
}

6.3为什么需要channel

1)前面使用全局变量加锁同步来解决goroutine的通讯并不完美

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

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

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

5)下面来讲以下channel

6.4 channel的基本介绍

1)channel本质是一个数据结构+队列

2)数据是先进先出

3)线程安全,多个goroutine访问时,不需要加锁,就是说channel本身就是线程安全的

4)channel有类型的,一个string的channel只能存放string类型的数据

6.5定义/声明管道

var 变量名 chan 数据类型

举例:

var intChan chan int 存放int类型

var mapChan chan map[int]string mapChan存放map[int]string类型

var perChan chan Person...

说明

channel是引用类型

channel必须初始化才能写入数据,即make后才能用

管道是有类型的,intChan只能写入整数int

6.6管道的初始化,写入数据到管道,从管道读取数据,注意事项

package Goroutine

import "fmt"

func Test5()  {
	//演示一下管道的使用
	//1,创建一个可以存放3个int类型的管道
	var intChan chan int
	intChan=make(chan int,3)
	//2,看看intChan是什么
	fmt.Printf("值:%V 地址:%p\n",intChan,&intChan)
	//3,向管道写入数据
	intChan<-10
	num:=211
	intChan<-num
	intChan<-50
	//intchan<-99注意不要超过它的容量
	//4,看看管道的长度和cap(容量)
	fmt.Printf("长度 len=%v cap=%v\n",len(intChan),cap(intChan))
	//5,从管道里读取数据
	var num2 int
	num2=<-intChan
	fmt.Println("num2=",num2)
	fmt.Printf("长度 len=%v cap=%v\n",len(intChan),cap(intChan))
	//6,在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报告deadlock
	num3:=<-intChan
	num4:=<-intChan
	num5:=<-intChan
	fmt.Println("num3=",num3,"num4=",num4,"num5=",num5)

}

6.7channel使用的注意事项

1)channel只能存放指定的数据类型

2)channel的数据放满后,就不能再放入了

3)如果从channel取出数据后就可以继续放入

4)在没有使用协程的情况下,如果channel数据取完了,再取就会报错

6.8读写channel案例演示

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

2)创建一个mapChan,最多可以存放1个map[string]string演示写入和读取

3)创建一个结构体变量cat,创建一个管道,演示catChan的存取

4)创建一个allchan可以存放任意数据类型的变量

7,练习

1)创建一个person结构体[Name,Age,Address]

2)创建10个Person实例,并放入到channel中

3)遍历channel,将各个person实例信息显示在终端

package Goroutine

import "fmt"

type Person struct {
	name string
	Age int
	Address string
}
func Test9()  {
	var PersonChan chan Person
	PersonChan=make(chan Person,10)
	for i:=0;i<10;i++{
		PersonChan<-Person{"wang",i,"asa"}
	}
	for len(PersonChan)!=0{
		fmt.Println(<-PersonChan)
	}

}

8,chaneel的遍历和关闭

8.1channe的关闭

使用内置函数close可以关闭channel,当channel关闭后,就不能再向channel写数据了,但是仍然可以从该channel读取数据

8.2channel的遍历

channel支持for-range的方式进行遍历

1)在遍历时,如果channel没有关闭,则会出现deadlock错误

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

8.3channel遍历和关闭的案例演示

8.4应用实例1

请完成goroutine和channel协同工作的案例

1)开启一个writeData协程,向管道intChan中写入五十个数

2)开启一个readDate协程,从管道intChan中读取writeData写入数据

3)注意:WriteData和readData操作的是同一个管道

4)主线程需要等待writeData和readData协程都完成工作才能退出管道

package Goroutine

import (
	"fmt"
)

//writeData
func writeData(intChan chan int)  {
	for i:=1;i<=50;i++{
		intChan<-i
		fmt.Println("writeData",i)
	}
	close(intChan)
}
//readData
func readData(intChan chan int,exitChan chan bool)  {
	for{
		v,ok:=<-intChan
		if !ok{
			break
		}
		fmt.Println("readData",v)
		//time.Sleep(time.Second)
	}

	exitChan<-true
	close(exitChan)
}

func Test12()  {
	//创建两个管道
	intChan:=make(chan int,50)
	exitChan:=make(chan bool,1)
	go writeData(intChan)
	//time.Sleep(time.Second)
	go readData(intChan,exitChan)
	//time.Sleep(time.Second)
	for{
		_,ok:=<-exitChan
		fmt.Println("sa")
		if!ok{
			break
		}
	}
}

8.5应用实例2-阻塞

如果只是向管道写入数据,而没有读取,就会出现阻塞而dead lock,原因是intChan容量是10,而代码writeData会写入50个数据,因此会阻塞在writeData的ch<-i

8.6应用实例3

要求统计1-200000的数字中,哪些是素数?我们这里采用goroutine和channnel来完成,测试数据是80000

分析思路:

传统的方法是一个循环,判断各个数字是不是素数

现在使用并发知识,将统计素数的任务分配给4个goroutine去完成

package Goroutine

import (
	"fmt"
	"time"
)

//向intChan放入1-8000个数字
func putNum(intChan chan int){
	for i:=1;i<=8000;i++{
		intChan<-i
	}
	//关闭intChan
	close(intChan)
}
//从intChan中取出数据,并判断是不是素数,如果是就放入到primeChan
func primeNum(intChan chan int,primeChan chan int,exitChan chan bool){
	var flag bool
	for{
		time.Sleep(time.Millisecond*10)
		num,ok:=<-intChan
		if!ok{//intChan取不到
			break
		}
		flag=true
		for i:=2;i<num;i++{
			if num%i==0{
				flag=false
				break
			}
		}
		if flag{
			//就将这个数放入到primeChan
			primeChan<-num
		}
	}
	fmt.Println("有一个primeNUM因为取不到数据,退出")
	//向exitChan写入true
	exitChan<-true

}
func Test13()  {
	intChan:=make(chan int,1000)
	primeChan:=make(chan int,2000)//放入结果
	//标识退出管道
	exitChan:=make(chan bool,4)
	go putNum(intChan)
	//开启四个协程
	for i:=0;i<4;i++{
		go primeNum(intChan,primeChan,exitChan)
	}
	//这里我们主线程进行处理
	//直接
	go func() {
		for i:=0;i<4;i++{
			fmt.Println("exitchan",<-exitChan)
		}
		//当我们从exitChan中取出来四个结果后,就可以放心的关闭prprimeChan
		close(primeChan)
	}()

	//遍历我们的primeChan
	for{
		res,ok:=<-primeChan
		if !ok{
			break
		}
		fmt.Println("素数",res)

	}
	fmt.Println("main线程退出")
}

9,channel使用细节和注意事项

1)channel可以声明为只读,或者只写性质

2)channel只读和只写的最佳实践案例

3)使用select可以解决从管道读取数据的阻塞问题

package Goroutine

import (
	"fmt"
	"time"
)

func Test15()  {
	//1,定义一个管道存10个数据
	intChan:=make(chan int,10)
	for i:=0;i<10;i++{
		intChan<-i
	}
	//2,定义一个管道存5个数据string
	stringChan:=make(chan string,5)
	for i:=0;i<5;i++ {
		stringChan<-"sww"+fmt.Sprintf("%d",i)
	}
	//在传统的方法中我们不关闭会阻塞而导致deadlock
	//我们可以使用select方式解决
	for  {
		select{
		//注意,这里如果intchan一直没有关闭,不会一直阻塞而deadlock,会自动到下一个case匹配
			case v:=<-intChan:
				fmt.Println("数据",v)
				time.Sleep(time.Second)
			case v:=<-stringChan:
				fmt.Println("字符串",v)
				time.Sleep(time.Second)
		default:
			fmt.Println("都取不到了")
			return
		}
	}
}

4)goroutine中使用recover解决协程中出现的panic,导致程序出现问题,这时我们可以在goroutine中使用recover来捕获panic进行处理,这样即使这个协程发生问题,但是主线程仍然不受影响,可以继续执行

package Goroutine

import (
	"fmt"
	"time"
)

func sayHello()  {
	for i:=0;i<10;i++{
		time.Sleep(time.Second)
		fmt.Println("hello world")
	}
}
//函数
func errorrecover(){
	//这里我们可以使用defer+recover
	defer func() {
		//捕获test抛出的panic
		err:=recover()
		if err!=nil{
			fmt.Println("test()发生错误",err)
		}
	}()
	var m1 map[int]string
	m1[0]="ss"
}

func Test16()  {
	go sayHello()
	go errorrecover()
	for i:=0;i<10;i++{
		fmt.Println(i)
		time.Sleep(time.Second)
	}
}


码字不易,还望点个赞,点个关注多多支持一下!谢谢!

(免费订阅,永久学习)学习地址: Dpdk/网络协议栈/vpp/OvS/DDos/NFV/虚拟化/高性能专家-学习视频教程-腾讯课堂

更多DPDK相关学习资料有需要的可以自行报名学习,免费订阅,永久学习,或点击这里加qun免费
领取,关注我持续更新哦! !  

原文链接;https://zhuanlan.zhihu.com/p/419186816 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/25473.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Metabase学习教程:提问-5

多级聚合 如何使用查询生成器对多个部分提出问题。 许多分析问题只需四个步骤即可回答&#xff1a; 连接需要的表得到需要的信息。过滤数据使其仅包含期望的记录。分组和聚合这些数据&#xff0c;创造你所需要的价值。可视化结果&#xff0c;方便直观的理解数据告诉了你什么…

SpringBoot 集成JWT实现登录认证

如果文章对你有帮助欢迎【关注❤️❤️❤️点赞&#x1f44d;&#x1f44d;&#x1f44d;收藏⭐⭐⭐】一键三连&#xff01;一起努力&#xff01; 一、JWT简介 JSON Web Token&#xff08;JWT&#xff09;是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递…

基于粒子群优化算法的最佳方式设置无线传感器节点的位置,以减轻由于任何能量耗尽节点而产生的覆盖空洞(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

数据脱敏的安全管理

什么是数据脱敏 我们需要先说下什么是敏感数据&#xff0c; 敏感数据泛指个人信息&#xff08;姓名、电话、住址、健康信息、证件等数据&#xff09;、涉及需要保护的数据。 数据脱敏&#xff0c;是将敏感数据按照一定的规则对敏感数据进行变形&#xff0c;达到保护用户信息安…

提升80%上云集成效率, TA是如何做到的

摘要&#xff1a;基于华为云开天aPaaS&#xff0c;提升80%上云集成效率&#xff0c;降低50%集成成本没有充足资金&#xff0c;没有足够的项目规划和过渡时间&#xff0c;也没有经验丰富的IT团队支持&#xff0c;中小企业的上云路可谓是困难重重。如何帮助企业高效上云、实现降本…

【Globalmapper中文入门到精通系列实验图文教程】(附配套实验数据持续更新)

【Globalmapper中文版入门到精通系列实验图文教程】&#xff08;附配套实验数据持续更新&#xff09; 文章目录一、专栏简介二、文章目录三、数据目录四、传送门一、专栏简介 本专栏为GlobalMapper中文入门实战精品教程&#xff0c;内容主要涉及&#xff1a;Globalmapper23软件…

【Oracle】数据库账号频繁被锁问题解决

文中使用的Oracle版本为11g。 今天在测试环境中遇到了一个问题&#xff0c;如下图&#xff1a; 所有的数据库客户端访问Oracle11g都出现了上面的提示“ORA-28000: the account is locked”&#xff0c;一开始其实并不知道是什么原因引起的问题&#xff0c;到后面才发现是登录错…

Kotlin 开发Android app(九):Android两大布局LinearLayout和RelativeLayout

Kotlin 的基本特性就先写到这里&#xff0c;我们这个系列的定位是基础&#xff0c;也就是能用就好&#xff0c;够用就好&#xff0c;我们不会举太多的例子&#xff0c;但是这些都是最经常用到的特性。 从这节开始就是Kotlin和android 进行结合&#xff0c;使用Kotlin进行安卓应…

基于Spring Cloud的架构使用学习升级之路

引言 Spring Cloud全家桶用了挺长时间了&#xff0c;很长一段时间都是基于已有的架构进行需求研发。今年成为团队技术负责人&#xff0c;承担了新的项目&#xff0c;这是很好的一个机会&#xff0c;于是开启了项目架构升级之路。 架构&#xff0c;是团队项目的根基。在一个团…

数字信号处理-5-傅里叶分析

1 傅里叶系数 傅里叶级数用公式如下&#xff1a; a0、a1、a2、a3…b1、b2、b3…叫做傅里叶系数。cosnx 或 sinnx 中的 n 对应着频率&#xff0c;决定 sin、cos 大小的系数是 an、bn。 2 傅里叶变换 步骤1 求傅里叶系数 从原波形 F(x) 中求傅里叶系数中的 a0、a1、a2、a3……

IPD-产品需求管理过程(1)

一、产品需求管理模型 在确定客户需求时,要考虑影响用户购买标准的八类基本需求($APPEALS),并基于客户视角进行详细分解,形成有针对性的产品。 1.1、需求管理业务流程 二、需求收集流程 2.1、需求收集的来源 路标规划:通过市场管理流程分析,落实到路标规划中的需求…

python中StringIO和BytesIO

1. 类文件对象 最常见的io操作是将磁盘中的文件读到内存以及内存内容写入文I件。还有一种内存和内存之间的IO&#xff0c;叫类文件对象&#xff0c;python中的StringIO和BytesIO就是类文件对象&#xff0c;通俗解释即&#xff1a;像操作文件一样在内存中操作字符串和二进制内容…

基于FPGA的SD卡的数据读写实现(SD NAND FLASH)

文章目录 1、存储芯片分类 2、NOR Flash 与 NAND Flash的区别 3、什么是SD卡&#xff1f; 4、什么是SD NAND&#xff1f; 5、SD NAND的控制时序 6、FPGA实现SD NAND读写 6.1、设计思路 6.2、仿真结果 6.3、实验结果 1、存储芯片分类 目前市面上的存储芯片&#xff0…

如何使用腾讯云提供的WordPress应用镜像搭建博客网站系统!

之前也有写过搭建WordPress的教程&#xff0c;如何使用轻量应用服务器搭建WordPress个人博客使用的是宝塔面板一件搭建的方式&#xff0c;但是还是有一些麻烦&#xff0c;这里我们之间使用腾讯云提供的WordPress应用镜像搭建&#xff0c;感兴趣小伙伴可以参考以下&#xff01; …

嵌入式分享合集108

一、PLC串口通讯的基本知识 这几天弄plc都要神经了 尤其西门子的 太烦了 s7200cn s7200smart s1200 编程软件都不一样~~服 &#xff0c; 然后接线也很烦 好了 正题 电气作业人员在使用PLC的时候会接触到很多的通讯协议以及通讯接口 什么是串口通讯&#xff1f; 串口通讯的使…

阿里强势推出Spring源码进阶宝典:思维脑图+视频教程+笔记文档

这不是准备跳槽了&#xff0c;所以最近摸鱼比较多一些&#xff0c;老大默许了&#xff0c;我觉得我老大还是很好的。也在网上看了一些资料&#xff0c;但是&#xff0c;我发现很多讲解注解的时候&#xff0c;对于一些可以直接点击源码查看的内容讲解的占多数&#xff0c;但是授…

ThreadPoolExecutor 线程池参数详解,执行流程

线程池的使用: public static void main(String[] args) {ThreadFactory sThreadFactory new ThreadFactory() {private final AtomicInteger mCount new AtomicInteger(1);Overridepublic Thread newThread(Runnable r) {int andIncrement mCount.getAndIncrement();return…

成熟的汽车衡称重软件,应具备哪些品质

每台汽车都配电子计算机、打印机各一台&#xff0c;并配相应称重管理软件。制造厂商开发的最新软件应免费及时为买方升级。自动称重系统管理软件选用国内成熟产品&#xff0c;至少在国内有10套以上稳定运行业绩&#xff0c;需配一套容量为2KVA&#xff0c;220V的UPS电源至少满足…

Guava LongMath类

Guava LongMath类 Guava LongMath类 LongMath提供long基础类型的实用方法。 类声明 以下是com.google.common.math.LongMath类的声明&#xff1a; GwtCompatible(emulatedtrue) public final class LongMath extends Object 方法 方法继承 这个类继承了以下类方法&#xf…

二、数据库查询语句(多表查询篇)

二、数据库查询语句(多表查询篇) 1、笛卡尔积 ​ 前面涉及的都是单张表的查询&#xff0c;如果我们的查询条件相对比较复杂&#xff0c;需要涉及多张表进行查询&#xff0c;如果是两张无关的表联合查询&#xff0c;列出所有的可能的结果&#xff0c;如下图&#xff1a; 如果没…