go并发编程-runtime、Channel与Goroutine
1. runtime包
1.1.1. runtime.Gosched()
让出CPU时间片,重新等待安排任务(大概意思就是本来计划的好好的周末出去烧烤,但是你妈让你去相亲,两种情况第一就是你相亲速度非常快,见面就黄不耽误你继续烧烤,第二种情况就是你相亲速度特别慢,见面就是你侬我侬的,耽误了烧烤,但是还馋就是耽误了烧烤你还得去烧烤)
package mainimport ("fmt""runtime"
)func main() {go func(s string) {for i := 0; i < 2; i++ {fmt.Println(s)}}("world")// 主协程for i := 0; i < 2; i++ {// 切一下,再次分配任务runtime.Gosched()fmt.Println("hello")}
}
1.1.2. runtime.Goexit()
退出当前协程(一边烧烤一边相亲,突然发现相亲对象太丑影响烧烤,果断让她滚蛋,然后也就没有然后了)
package mainimport ("fmt""runtime"
)func main() {go func() {defer fmt.Println("A.defer")func() {defer fmt.Println("B.defer")// 结束协程runtime.Goexit()defer fmt.Println("C.defer")fmt.Println("B")}()fmt.Println("A")}()for {}
}
1.1.3. runtime.GOMAXPROCS
Go运行时的调度器使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行Go代码。默认值是机器上的CPU核心数。例如在一个8核心的机器上,调度器会把Go代码同时调度到8个OS线程上(GOMAXPROCS是m:n调度中的n)。
Go语言中可以通过runtime.GOMAXPROCS()函数设置当前程序并发时占用的CPU逻辑核心数。
Go1.5版本之前,默认使用的是单核心执行。Go1.5版本之后,默认使用全部的CPU逻辑核心数。
我们可以通过将任务分配到不同的CPU逻辑核心上实现并行的效果,这里举个例子:
func a() {for i := 1; i < 10; i++ {fmt.Println("A:", i)}
}func b() {for i := 1; i < 10; i++ {fmt.Println("B:", i)}
}func main() {runtime.GOMAXPROCS(1)go a()go b()time.Sleep(time.Second)
}
两个任务只有一个逻辑核心,此时是做完一个任务再做另一个任务。 将逻辑核心数设为2,此时两个任务并行执行,代码如下。
func a() {for i := 1; i < 10; i++ {fmt.Println("A:", i)}
}func b() {for i := 1; i < 10; i++ {fmt.Println("B:", i)}
}func main() {runtime.GOMAXPROCS(2)go a()go b()time.Sleep(time.Second)
}
Go语言中的操作系统线程和goroutine的关系:
- 1.一个操作系统线程对应用户态多个goroutine。
- 2.go程序可以同时使用多个操作系统线程。
- 3.goroutine和OS线程是多对多的关系,即m:n。
2. Channel
2.1. channel
单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。
虽然可以使用共享内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问题。为了保证数据交换的正确性,必须使用互斥量对内存进行加锁,这种做法势必造成性能问题。
Go语言的并发模型是CSP(Communicating Sequential Processes),提倡通过通信共享内存而不是通过共享内存而实现通信。
如果说goroutine是Go程序并发的执行体,channel就是它们之间的连接。channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。
Go 语言中的通道(channel)是一种特殊的类型。通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。每一个通道都是一个具体类型的导管,也就是声明channel的时候需要为其指定元素类型。
2.2. channel类型
channel是一种类型,一种引用类型。声明通道类型的格式如下:
var 变量 chan 元素类型
举几个例子:
var ch1 chan int // 声明一个传递整型的通道var ch2 chan bool // 声明一个传递布尔型的通道var ch3 chan []int // 声明一个传递int切片的通道
2.3. 创建channel
通道是引用类型,通道类型的空值是nil。
var ch chan int
fmt.Println(ch) // <nil>
声明的通道后需要使用make函数初始化之后才能使用。
创建channel的格式如下:
make(chan 元素类型, [缓冲大小])
channel的缓冲大小是可选的。
举几个例子:
ch4 := make(chan int)
ch5 := make(chan bool)
ch6 := make(chan []int)
2.4. channel操作
通道有发送(send)、接收(receive)和关闭(close)三种操作。
发送和接收都使用<-符号。
现在我们先使用以下语句定义一个通道:
ch := make(chan int)
发送
将一个值发送到通道中。
ch <- 10 // 把10发送到ch中
接收
从一个通道中接收值。
x := <- ch // 从ch中接收值并赋值给变量x
<-ch // 从ch中接收值,忽略结果
关闭
我们通过调用内置的close函数来关闭通道。
close(ch)
关于关闭通道需要注意的事情是,只有在通知接收方goroutine所有的数据都发送完毕的时候才需要关闭通道。通道是可以被垃圾回收机制回收的,它和关闭文件是不一样的,在结束操作之后关闭文件是必须要做的,但关闭通道不是必须的。
关闭后的通道有以下特点:
1.对一个关闭的通道再发送值就会导致panic。2.对一个关闭的通道进行接收会一直获取值直到通道为空。3.对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。4.关闭一个已经关闭的通道会导致panic。
2.5. 无缓冲的通道
无缓冲的通道又称为阻塞的通道。我们来看一下下面的代码:
func main() {ch := make(chan int)ch <- 10fmt.Println("发送成功")
}
上面这段代码能够通过编译,但是执行的时候会出现以下错误:
fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan send]:main.main().../src/github.com/pprof/studygo/day06/channel02/main.go:8 +0x54
为什么会出现deadlock错误呢?
因为我们使用ch := make(chan int)创建的是无缓冲的通道,无缓冲的通道只有在有人接收值的时候才能发送值。就像你住的小区没有快递柜和代收点,快递员给你打电话必须要把这个物品送到你的手中,简单来说就是无缓冲的通道必须有接收才能发送。
上面的代码会阻塞在ch <- 10这一行代码形成死锁,那如何解决这个问题呢?
一种方法是启用一个goroutine去接收值,例如:
func recv(c chan int) {ret := <-cfmt.Println("接收成功", ret)
}
func main() {ch := make(chan int)go recv(ch) // 启用goroutine从通道接收值ch <- 10fmt.Println("发送成功")
}
无缓冲通道上的发送操作会阻塞,直到另一个goroutine在该通道上执行接收操作,这时值才能发送成功,两个goroutine将继续执行。相反,如果接收操作先执行,接收方的goroutine将阻塞,直到另一个goroutine在该通道上发送一个值。
使用无缓冲通道进行通信将导致发送和接收的goroutine同步化。因此,无缓冲通道也被称为同步通道。
2.6. 有缓冲的通道
解决上面问题的方法还有一种就是使用有缓冲区的通道。
我们可以在使用make函数初始化通道的时候为其指定通道的容量,例如:
func main() {ch := make(chan int, 1) // 创建一个容量为1的有缓冲区通道ch <- 10fmt.Println("发送成功")
}
只要通道的容量大于零,那么该通道就是有缓冲的通道,通道的容量表示通道中能存放元素的数量。就像你小区的快递柜只有那么个多格子,格子满了就装不下了,就阻塞了,等到别人取走一个快递员就能往里面放一个。
我们可以使用内置的len函数获取通道内元素的数量,使用cap函数获取通道的容量,虽然我们很少会这么做。
2.7. close()
可以通过内置的close()函数关闭channel(如果你的管道不往里存值或者取值的时候一定记得关闭管道)
package mainimport "fmt"func main() {c := make(chan int)go func() {for i := 0; i < 5; i++ {c <- i}close(c)}()for {if data, ok := <-c; ok {fmt.Println(data)} else {break}}fmt.Println("main结束")
}
2.8. 如何优雅的从通道循环取值
当通过通道发送有限的数据时,我们可以通过close函数关闭通道来告知从该通道接收值的goroutine停止等待。当通道被关闭时,往该通道发送值会引发panic,从该通道里接收的值一直都是类型零值。那如何判断一个通道是否被关闭了呢?
我们来看下面这个例子:
// channel 练习
func main() {ch1 := make(chan int)ch2 := make(chan int)// 开启goroutine将0~100的数发送到ch1中go func() {for i := 0; i < 100; i++ {ch1 <- i}close(ch1)}()// 开启goroutine从ch1中接收值,并将该值的平方发送到ch2中go func() {for {i, ok := <-ch1 // 通道关闭后再取值ok=falseif !ok {break}ch2 <- i * i}close(ch2)}()// 在主goroutine中从ch2中接收值打印for i := range ch2 { // 通道关闭后会退出for range循环fmt.Println(i)}
}
从上面的例子中我们看到有两种方式在接收值的时候判断通道是否被关闭,我们通常使用的是for range的方式。
2.9. 单向通道
有的时候我们会将通道作为参数在多个任务函数间传递,很多时候我们在不同的任务函数中使用通道都会对其进行限制,比如限制通道在函数中只能发送或只能接收。
Go语言中提供了单向通道来处理这种情况。例如,我们把上面的例子改造如下:
func counter(out chan<- int) {for i := 0; i < 100; i++ {out <- i}close(out)
}func squarer(out chan<- int, in <-chan int) {for i := range in {out <- i * i}close(out)
}
func printer(in <-chan int) {for i := range in {fmt.Println(i)}
}func main() {ch1 := make(chan int)ch2 := make(chan int)go counter(ch1)go squarer(ch2, ch1)printer(ch2)
}
其中,
1.chan<- int是一个只能发送的通道,可以发送但是不能接收;2.<-chan int是一个只能接收的通道,可以接收但是不能发送。
在函数传参及任何赋值操作中将双向通道转换为单向通道是可以的,但反过来是不可以的。
2.10. 通道总结
channel常见的异常总结,如下图:
注意:关闭已经关闭的channel也会引发panic。
3. Goroutine池
3.1. worker pool(goroutine池)
- 本质上是生产者消费者模型
- 可以有效控制goroutine数量,防止暴涨
- 需求:
- 计算一个数字的各个位数之和,例如数字123,结果为1+2+3=6
- 随机生成数字进行计算
- 控制台输出结果如下:
package mainimport ("fmt""math/rand"
)type Job struct {// idId int// 需要计算的随机数RandNum int
}type Result struct {// 这里必须传对象实例job *Job// 求和sum int
}func main() {// 需要2个管道// 1.job管道jobChan := make(chan *Job, 128)// 2.结果管道resultChan := make(chan *Result, 128)// 3.创建工作池createPool(64, jobChan, resultChan)// 4.开个打印的协程go func(resultChan chan *Result) {// 遍历结果管道打印for result := range resultChan {fmt.Printf("job id:%v randnum:%v result:%d\n", result.job.Id,result.job.RandNum, result.sum)}}(resultChan)var id int// 循环创建job,输入到管道for {id++// 生成随机数r_num := rand.Int()job := &Job{Id: id,RandNum: r_num,}jobChan <- job}
}// 创建工作池
// 参数1:开几个协程
func createPool(num int, jobChan chan *Job, resultChan chan *Result) {// 根据开协程个数,去跑运行for i := 0; i < num; i++ {go func(jobChan chan *Job, resultChan chan *Result) {// 执行运算// 遍历job管道所有数据,进行相加for job := range jobChan {// 随机数接过来r_num := job.RandNum// 随机数每一位相加// 定义返回值var sum intfor r_num != 0 {tmp := r_num % 10sum += tmpr_num /= 10}// 想要的结果是Resultr := &Result{job: job,sum: sum,}//运算结果扔到管道resultChan <- r}}(jobChan, resultChan)}
}
参考文章:go协程之Channel - 范斯猫
相关文章:

go并发编程-runtime、Channel与Goroutine
1. runtime包 1.1.1. runtime.Gosched() 让出CPU时间片,重新等待安排任务(大概意思就是本来计划的好好的周末出去烧烤,但是你妈让你去相亲,两种情况第一就是你相亲速度非常快,见面就黄不耽误你继续烧烤,第二种情况就是你相亲速度…...

HTTP概述
HTTP概述 HTTP(Hypertext Transfer Protocol)是一种用于传输超文本的应用层协议。它是在客户端和服务器之间进行通信的基础,常用于 Web 应用中。在 Java 后端开发中,HTTP 扮演着重要的角色。以下是Java 后端视角下的 HTTP 概述&a…...

ubuntu20配置mysql8
首先更新软件包索引运行 sudo apt update命令。然后运行 sudo apt install mysql-server安装MySQL服务器。 安装完成后,MySQL服务将作为systemd服务自动启动。你可以运行 sudo systemctl status mysql命令验证MySQL服务器是否正在运行。 连接MySQL 当MySQL安装…...

CPU-Cache结构查看
参考【Ubuntu 查看 CPU 缓存】 本文主要介绍cpu的cache查看,以供读者能够理解该技术的定义、原理、应用。 🎬个人简介:一个全栈工程师的升级之路! 📋个人专栏:计算机杂记 🎀CSDN主页 发狂的小花…...

Wireshark网络协议分析 - Wireshark速览
在我的博客阅读本文 文章目录 1. 版本与平台2. 快速上手2.1. 选择网络接口进行捕获(Capture)2.2. 以Ping命令为例进行抓包分析2.3. 设置合适的过滤表达式2.4. 数据包详情2.5. TCP/IP 四层模型 3. 参考资料 1. 版本与平台 Wireshark是一个开源的网络数据…...

查看进程创建的所有线程
ps 在ps命令中,“-T”选项可以开启线程查看。下面的命令列出了由进程号为的进程创建的所有线程。 ps -T -p <pid>top op命令可以实时显示各个线程情况。要在top输出中开启线程查看,请调用top命令的“-H”选项,该选项会列出所有Linux…...

汽车软件开发模式的5个特点
汽车软件开发属于较为复杂的系统工程,经常让来自不同知识背景的工程师在观点交锋时出现分歧。在解决复杂性和对齐讨论基准时,可以通过勾勒出讨论对象最关键的几个特征来树立典型概念。本文旨在通过5个典型特点的抽取,来勾勒出汽车软件开发模式…...

双屏联动系统在展厅设计中的互动类型与效果
随着各项多媒体技术的快速发展,让展厅中的各类展项得到技术升级,其中作为电子设备中最基础的显示技术,不仅优化了内容的展示质量,还实现了更具互动性的创新技术,如双屏联动系统就是当前展厅设计中最常见的技术类型之一…...

STM32F407移植OpenHarmony笔记5
继上一篇笔记,搭建好STM32的编译框架,编译出来的OHOS_Image.bin并不能跑不起来。 今天要在bsp里面添加一些代码,让程序能跑起来。 先从裸机点亮LED灯开始,准备以下3个文件:startup和system文件可以用OHOS官方代码。 /device/boar…...

点击其他区域隐藏弹出框效果
一般下拉框或者选择框,持久展示时会给用户显示的隐藏方式,如点击事件后。也可以添加隐式的隐藏方式,如点击弹出框之外的区域。 CSS方法-focus伪类 当触发的元素是可以focus,以输入框为例。 可以将弹出框出现的时机设置在input:…...

Python一些可能用的到的函数系列123 ATimer2-时间偏移
说明 之前确定了时间轴(千年历),以及时间的转换方法。其中时间轴的数据将会存储在集群,以及通过RedisOrMongo保存部分常用的数据。 本次讨论时间偏移的度量问题。 内容 1 两种形式 我们提到时间时,通常会有两种方…...

企业微信主体变更 怎么操作?
企业微信变更主体有什么作用?当我们的企业因为各种原因需要注销或已经注销,或者运营变更等情况,企业微信无法继续使用原主体继续使用时,可以申请企业主体变更,变更为新的主体。企业微信变更主体的条件有哪些࿱…...

《区块链简易速速上手小册》第8章:区块链的技术挑战(2024 最新版)
文章目录 8.1 可扩展性问题8.1.1 基础知识8.1.2 主要案例:比特币的可扩展性挑战8.1.3 拓展案例 1:以太坊的可扩展性改进8.1.4 拓展案例 2:侧链和分层解决方案 8.2 安全性与隐私8.2.1 基础知识8.2.2 主要案例:比特币交易的安全性8.…...

基于STM32的云上OneNET智慧大棚(包含程序设计报告)
目录 概要 作品介绍 设计原理 设计作品结构 软件部分 概要 为了解决传统农业生产效率低下,消耗大量的人力物力,不能精确的对农作物实现监控的问题,从而最大限度的提高农业生产力,实现优质、高产、低耗、环保的可持续发展物联…...

11.scala函数进阶
目录 概述函数scala函数和方法的区别第一种第二种方法转函数 函数柯里化 结束 概述 函数 vs 方法 1.定义 方法:def函数:> 2.方法本质上是一个特殊的函数3.函数是一等公民,函数可以当做值来传递,方法的参数,返回值…...

在WebSocket中使用Redis出现空指针异常解决方案
文章目录 在WebSocket中使用Redis1.问题描述2.原因3.解决步骤1.新建一个SpringUtil.java类,通过getBean的方法主动获取实例2.在WebSocketSingleServer.java中导入 在WebSocket中使用Redis 1.问题描述 在controller 和 service中都可以正常使用Redis,在…...

问题:第十三届全国人民代表大会第四次会议召开的时间是()。 #经验分享#知识分享#媒体
问题:第十三届全国人民代表大会第四次会议召开的时间是()。 A. 2018年3月3日至3月11日 B. 2019年3月5日至3月11日 C. 2020年3月5日至3月11日 D. 2021年3月5日至3月11日 参考答案如图所示 问题:顾客满意是顾客对一件产品满足…...

《区块链简易速速上手小册》第10章:区块链的未来与趋势(2024 最新版)
文章目录 10.1 区块链的未来展望10.1.1 基础知识10.1.2 主要案例:区块链在金融领域的发展10.1.3 拓展案例 1:区块链在供应链管理中的应用10.1.4 拓展案例 2:区块链在身份管理和隐私保护中的应用 10.2 新兴技术与区块链的融合10.2.1 基础知识1…...

JVM工作原理与实战(三十一):诊断内存泄漏的原因
专栏导航 JVM工作原理与实战 RabbitMQ入门指南 从零开始了解大数据 目录 专栏导航 前言 一、诊断原因 二、MAT内存泄漏检测的原理 总结 前言 JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线…...

#{}和${}的区别
#{}和${}的区别 .本质区别:使用注意事项防止SQL注入排序like查询 . 在使用mybatis操作数据库的时候,我们在编写sql语言的时候,会遇到一个问题,就是在传参的时候,有两个符号#,$.这两个符号有什么异同呢,接下来,我就会带着大家对这个问题进行简单的探讨 本质区别: #执行的是预编…...

【数据结构】(三)树Tree
目录 1、基本概念 2、二叉树Binary Tree 3、树、森林与二叉树的转换 4、赫夫曼树Huffman Tree与赫夫曼编码Huffman Coding 1、基本概念 (1)树(Tree)是 n(n ≥\geq 1)个节点的有限集,n 0时称…...

扩展坞 接两个显示器
笔记本电脑如何外接两个显示器,达到三个屏同时显示? 3 笔记本有 2 个显示扩展接口 目前笔记本中最常见的就是 1 个 HDMI 口 1 个支持 DP 协议的 Type-C 口或 2 个支持 DP 协议的 Type-C 口,此时使用 HDMI 线和 Type-C 转接线分别直连两台显…...

鸿蒙 ArkTS 从数组内查找指定的数据
let arr [1, 2, 3, 4, 5]; let target 3; let result arr.filter(item > item target); let a String(result) 将数字转换成文本型 console.log(a); 亲爱的读者: 首先,我要感谢您抽出宝贵的时间阅读这篇文章。我深知,您的每一分每一…...

qemu 抓取linux kernel vmcore
一、背景 在qemu调试linux kernel时 有时我们会遇到dump 情况,这时可以通过gdb 方式连接分析dump, 但实际中我们用得更多的是离线dump 分析,分析的文件通常是vmcore(linux kernel panic 生成的coredump文件)或者ramdu…...

RabbitMQ 死信队列应用
1. 概念 死信队列(Dead Letter Queue)是在消息队列系统中的一种特殊队列,用于存储无法被消费的消息。消息可能会因为多种原因变成“死信”,例如消息过期、消息被拒绝、消息队列长度超过限制等。当消息变成“死信”时,…...

除毛可以用宠物空气净化器吗?猫用空气净化器哪些品牌吸毛好?
作为一位长期养猫的铲屎官,我深刻理解只有养猫人才懂的困扰,那就是家里到处都是猫毛和异味。我发现自从开始养猫之后,家里的空气质量变得不佳。猫毛和皮屑飞扬,而且室内空气中的污染物也越来越多。这种低质量的空气对我们的健康有…...

有趣的css - 好看的呼吸灯效果
整体效果 这个效果主要用 css3 的 animation 属性来实现的。 这个效果可以用作在网站的整体 Loading,也可以放在网站首屏当一个 banner 的背景也是非常棒的! 代码部分 html 部分代码: <div class"app"><span class&quo…...

二叉树-堆应用(1)
目录 堆排序 整体思路 代码实现 Q1建大堆/小堆 Q2数据个数和下标 TopK问题 整体思路 代码实现 Q1造数据CreateData Q2建大堆/小堆 建堆的两种方法这里会用到前面的向上/向下调整/交换函数。向上调整&向下调整算法-CSDN博客 堆排序 整体思路 建堆(直…...

猫头虎博主第10期赠书活动:《写给大家看的Midjourney设计书》
博主猫头虎的技术世界 🌟 欢迎来到猫头虎的博客 — 探索技术的无限可能! 专栏链接: 🔗 精选专栏: 《面试题大全》 — 面试准备的宝典!《IDEA开发秘籍》 — 提升你的IDEA技能!《100天精通Golang》…...

线程池相关的类学习
Executor public interface Executor {//执行任务void execute(Runnable command); }ExecutorService public interface ExecutorService extends Executor {//关闭线程池,不能再向线程池中提交任务,已存在与线程池中的任务会继续执行,直到…...