当前位置: 首页 > news >正文

掌握Go并发:Go语言并发编程深度解析

🏷️个人主页:鼠鼠我捏,要死了捏的主页 

🏷️系列专栏:Golang全栈-专栏

🏷️个人学习笔记,若有缺误,欢迎评论区指正 

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站AI学习网站。

前言

当我们开发一个Web服务时,我们希望可以同时处理成千上万的用户请求,当我们有大量数据要计算时,我们希望可以同时开启多个任务进行处理,随着硬件性能的提升以及应用数据的增长,有越来越多的场景需要高并发处理,而高并发是Go的强项。

在这篇文章中,我们就一起来探究一下Go并发编程!

目录

前言

并发与并行

并发

并行

Goroutines

什么是Goroutine

Goroutine的优势

启动Goroutine

关闭Goroutine

Channel

什么是Channel

创建Channel

Channel操作

发送与接收

关闭

遍历

无缓冲区Channel

有缓冲区Channel

Channel的串联

单方向的channel

select:多路复用

Goroutine泄漏

小结


并发与并行

在谈Go并发编程之前,我们需要对并发并行做一下区分。

并发

并发是指有多个任务处于运行状态,但无法确定到底任务的运行顺序,比如某一时间,有一个双核CPU,但有10个任务(线程),这些任务可能随机被分配到相同或者不同的核心上去运行,但是其运行顺序是不确定的。

并行

并行是指多个任务在某一个时刻同时运行,比如某一个时刻,一个双核心的CPU,两个核心同时都有一个任务在运行,那么就是说这两个任务是并行的。

Goroutines

Goroutine是 Go语言的并发单元。

什么是Goroutine

Goroutine,中文称为协程,我们可以把 Goroutine看作是一个轻量级的线程,而从代码层面来看,Goroutine就是一个独立运行的函数或方法。

Goroutine的优势

  1. 与线程相比,创建一个Goroutine的开销要小得多,一个Goroutine初始化时只需要2KB,而一个线程则要2MB,所以Go程序可以大量创建Goroutine进行并发处理。
  2. 虽然协程初始化只有2KB,但却可以根据需求动态扩展。
  3. Goroutine可以通过Channel互相通讯,而线程只能通过共享内存互相通讯。
  4. Goroutine由Go调度器进行调度,而线程则依赖系统的调度。

启动Goroutine

要启动一个Goroutine非常简单,只要在函数或者方法前面加上 go关键字就可以了:

package main func Hello(){fmt.Println("hello")
}func main(){go Hello()//匿名函数go func(){fmt.Println("My Goroutine")}()
}

程序启动后, main函数单独运行在一个 Goroutine中,这个 Goroutine称作 Main Goroutine,其他用go关键字启动的Goroutine各自运行。

如果你在控制台运行上面的程序,会发现在控制台根据没有任何输出,这是为什么呢?

原因在于虽然所有的Goroutine是独自运行的,但如果 Man Gorouine终止的话,那么所有 Goroutine 都会退出执行。

上面的示例中,我们启动的 Goroutine还没运行,main函数就执行结束了,因此整个程序就退出了。

package main import "time"func Hello(){fmt.Println("hello")
}func main(){go Hello()go func(){fmt.Println("My Goroutine")}()time.Sleep(time.Second)
}

上面的示例中,我们调用 time.Sleep()函数让 Main Goroutine休眠而不退出,这时候其他的Goroutine就可以在 Main Goroutine退出前执行。

关闭Goroutine

Go没有提供关闭Goroutine的机制,一般来说要让一个Goroutine停止有三种方式:

  • random Goroutine执行完成退出或者 return退出
  • main函数执行完成,所有Goroutine自然就会终止
  • 直接终止整个程序的执行(程序崩溃或调用os.Exit()),类似第2种方式。

Channel

Go并发编程的思想是:不要用共享内存来通讯,而是用通讯来共享内存。而这种通讯机制就是Channel。

什么是Channel

Channel是 Goroutine之间的通信机制,可以把 Channel理解为 Goroutine之间的一条管道,就像水可以从一个管道的一端流向另一端一样,数据也可以通过 Channel从一个 Goroutine流向其他的一个 Goroutine,以实现 Goroutine之间的数据通讯。

创建Channel

创建 Channel类型的关键字是 chan,在 chan后面跟一个其他的数据类型,用于表示该 channel可发送什么类型的数据,比如一个可以发送整数的 Channel其定义是:

var ch chan int

Channel的默认值为nil,Channel必须实例化后才能使用,使用 make()函数实例化:

ch = make(chan int)ch1 := make(chan int)

Channel与map一样是引用数据类型,在调用make()函数后,该Channel变量引用一块底层数据结构,因此当把channel变量传递给函数时,调用者与被调用者引用的是同一块数据结构。

Channel操作

Channel支持发送与接收两种操作,无论是发送还是接收,都是用 <-运算符。

发送与接收

向Channel发送数据时,运算符 <-放在channel变量的右边,运算符与Channel变量之间可以有空格:

ch <- x

接收Channel数据时,运算符 <-放在channel变量的左边且之间不能有空格:

x <-ch
x <- ch //错误写法

不接收channel的结果也是可以的:

<-ch

一个示例:

package mainimport "fmt"func main() {ch := make(chan int)go func(ch chan int) {ch <- 10}(ch)m := <-chfmt.Println(m)
}
关闭

使用内置 close可以关闭 Channel:

close(ch)

在关闭之后,如果再对该channel发送数据会导致panic错误:

close(ch)
ch <- x //panic

如果Channel中还有值未被接收,在关闭之后,还可以接收Channel里的值,如果没有值,则返回一个0值。

package mainimport "fmt"func main() {ch := make(chan int)go func(ch chan int) {ch <- 10close(ch) //关闭}(ch)m := <-chn := <-ch//10,0fmt.Println(m, n)
}

在从Channel接收值的时候,也可以多接收一个布尔值,如果为true,表示可以接收到有效值,如果没有值,则表示Channel被关闭且没有值:

n,ok := <-ch

关闭一个已经关闭的Channel会导致panic,关闭一个nil值的Channel也会导致panic。

遍历

Channel也可以用for...range语句来遍历:

package mainimport ("fmt""time"
)func main() {ch := make(chan int)go func(ch chan int) {ch <- 10ch <- 20}(ch)go func(ch chan int) {for c := range ch {fmt.Println(c)}}(ch)time.Sleep(time.Second)
}

无缓冲区Channel

上面的示例中,调用make()函数时没有指定第二个参数,这时创建的Channel称为无缓冲区Channel。

对于使用无缓冲区进行通讯的两个Goroutine来说,发送与阻塞都有可能会被阻塞,因此,本质使用无缓冲区的channel进行传输数据就是两个Goroutine之间的一次数据同步,无缓冲区的Channel又被称为同步Channel

package mainimport "fmt"func main() {ch := make(chan int)go func() {ch <- 10}()fmt.Println(<-ch)
}

有缓冲区Channel

调用 make()函数实例化 Channel时,也可以通过该函数的第二个参数指定 Channel的容量:

ch := make(chan int,2)

通过 cap()和 len()函数可以 Channel的长度:

cap(ch) //2
len(ch) //0
ch <- 10
len(ch) //1

对于带有缓冲区的Channel来说,当Channel容量满了,发送操作会阻塞,当Channel空的时候,接收操作会阻塞,只有当Channel未满且有数据时,发送与接收才不会发生阻塞。

Channel的串联

Channel是Goroutine之间沟通的管道,日常生活中,管道可以连接在一起,水可以从一条管道流向另一条管道,而Channel也是一样的,数据可以从一个Channel流向另一个Channel。

package mainimport "fmt"func main() {ch1 := make(chan int)ch2 := make(chan int)go func() {for x := 0; x < 100; x++ {ch1 <- x}close(ch1)}()go func() {for {x, ok := <-ch1if !ok {break}ch2 <- x * x}close(ch2)}()for x := range ch2 {fmt.Println(x)}
}

单方向的channel

利用Channel进行通讯的大部分应用场景是一个Goroutine作为生产者,只负责发送数据,而另一个Goroutine作为消费者,接收数据。

对于生产者来说,不会对Channel执行接收的操作,对于消费者来说不会对Channel执行发送的操作

在声明Channel变量将<-运算符放在 chan关键前面则该Channel只能执行接收操作:

//只允许接收
var ch1 <-chan int

在声明Channel变量将<-运算符放在 chan关键字后面可以则该Channel只能执行发送操作:

//只允许发送
var ch2 chan<- int

像我们前面那正常声明一个Channel变量,则允许对该Channel执行发送和接收操作:

//可以发送和接收
var ch3 chan int

从一个只能发送数据的channel接收数据无法通过编译:

var ch chan<- int
x := <-ch //报错

向一个只有接收数据的channel发送数据无法通过编译:

var ch <-chan int
ch <- 10 //报错

对一个只有接收操作的 Channel执行 close()也无法通过编译:

var ch <-chan int
close(ch) //报错

select:多路复用

前面的示例中,我们在一个 Goroutine中只向一个 Channel发送数据或者只从一个 Channel接收数据,因为如果同时向两个Channel接收或发送数据时,如果第一个Channel没有事件响应,程序会一直阻塞:

package mainimport ("fmt""time"
)func main() {ch1 := make(chan int)ch2 := make(chan int)go func(ch1 chan int, ch2 chan int) {fmt.Println("向ch1发送数据前")<-ch1fmt.Println("从ch2接收数据前")ch2 <- 1}(ch1, ch2)time.Sleep(1 * time.Second)
}

但很多场景下,我们需要在一个Goroutine中根据不同的Channel执行不同的操作:比如一个启动的Web服务器,在一个Goroutine中一边处理请求,一边监听信号量。要怎么做呢?

答案是:使用select语句,即多路复用,select语法类似switch语句,select语句块中可以包含多个case分支和一个default分支,每个case分支表示一个向Channel发送或接收的操作,select语句会选择可以执行的case分支来执行,如果没有,则执行default分支:

select {
case <-ch1:// do something
case x := <-ch2:// do somthing with x
case ch3 <- y:// do something
default:// dosomthing
}

下面我们通过一个案例来了解如何使用select语句,在这个例子中,我们模拟启动一个Web服务器处理来自用户的请求,而在处理请求的同时,还要可以根据接收的信息及时停止服务,我们在开启单独的一个Goroutine模拟向我们的Web发送停止信号:

package mainimport ("fmt""time"
)func main() {s := make(chan struct{})go func(s chan struct{}) {time.Sleep(time.Microsecond * 100)s <- struct{}{}}(s)MyWebServer(s)fmt.Println("服务已停止...")
}func MyWebServer(stop chan struct{}) {for {select {case <-stop:fmt.Println("服务器接收到停止信号")returndefault:}//模拟处理请求go HandleQuery()}
}func HandleQuery() {fmt.Println("处理请求...")
}

Goroutine泄漏

一个 Goroutine 由于从Channel接收或向 Channel 发送数据一直被阻塞,一直无法往下执行时,这种情况称为 Goroutine泄漏:

package mainimport "time"func main() {ch := make(chan int)go func() {ch <- 10}()time.Sleep(time.Second * 2)
}

Goroutine执行完成退出后,由Go内存回收机制进行回收,但是发生内存泄漏的Goroutine并不会被回收,因此要避免发生这种情况。

总结

Go在语言层面支持并发编程,只需要在函数或者方法前加上go关键字便可以启动一个Goroutine,而Channel作为Goroutine之间的通讯管道,可以非常方便Goroutine之间的数据通讯。

相关文章:

掌握Go并发:Go语言并发编程深度解析

&#x1f3f7;️个人主页&#xff1a;鼠鼠我捏&#xff0c;要死了捏的主页 &#x1f3f7;️系列专栏&#xff1a;Golang全栈-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&…...

创建一个多进程服务器和多线程服务器

多进程服务器 #include<myhead.h> #define PORT 8888 //端口号 #define IP "192.168.10.10" //IP地址//定义信号处理函数&#xff0c;用于回收僵尸进程 void handler(int signo) {if(signo SIGCHLD){while(waitpid(-1, NULL, WNOHAN…...

相机图像质量研究(18)常见问题总结:CMOS期间对成像的影响--CFA

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…...

18.谈谈你对JSON的理解

JSON 是一种基于文本的轻量级的数据交换格式。它可以被任何的编程语言读取和作为数据格式来传递。 在项目开发中&#xff0c;使用 JSON 作为前后端数据交换的方式。在前端通过将一个符合 JSON 格式的数据结构序列化为 JSON 字符串&#xff0c;然后将它传递到后端&#xff0c;后…...

绝地求生:“觉醒之旅”通行证曝光,西游主题通行证及成长型武器即将上线

随着27赛季即将结束&#xff0c;有关28.1版本的皮肤及通行证内容也被爆料出来&#xff0c;本次通行证为工坊通行证&#xff0c;和去年四圣兽通行证为同一类型&#xff0c;将于2月7日更新至正式服 除了通行证获取工坊币还是可以开箱获取并兑换一些奖励 先看通行证 四个套装应该分…...

JS如何判断普通函数与异步(async)函数

这里可以先打印一下普通函数和异步&#xff08;async&#xff09;函数的结构&#xff0c;如下图 可以看出两者原型链&#xff0c;普通函数的原型链指向的是一个函数&#xff0c;异步&#xff08;async&#xff09;函数原型链指向的是一个AsyncFunction&#xff0c;这时就会想到…...

ndk-r20b 编译 boost 1.74。

ndk-r20b 编译 boost 1.74&#xff0c;这是 ndk-r20b 支持得最大 boost 版本&#xff0c;再大就没法编译支持了&#xff0c;本文介绍方法是完整编译&#xff0c;不需要完整编译请转移到github&#xff0c;boost for android 得开源项目。 1.74 boost &#xff0c;安卓上面得版本…...

尚硅谷最新Node.js 学习笔记(四)

目录 八、express框架 8.1、express介绍 8.2、express使用 express下载 express初体验 8.3、express路由 什么是路由&#xff1f; 路由的使用 获取请求参数 获取路由参数 8.4、express响应设置 8.5、express中间件 什么是中间件&#xff1f; 中间件的作用 中间件…...

掌握XGBoost:GPU 加速与性能优化

导言 XGBoost是一种强大的机器学习算法&#xff0c;但在处理大规模数据时&#xff0c;传统的CPU计算可能会变得缓慢。为了提高性能&#xff0c;XGBoost可以利用GPU进行加速。本教程将介绍如何在Python中使用XGBoost进行GPU加速以及性能优化的方法&#xff0c;并提供相应的代码…...

【2024年毕设系列】如何使用Anaconda和Pycharm

【2024年毕设系列】如何使用Anaconda和Pycharm 视频教程地址&#xff1a;【2024毕设系列】Anaconda和Pycharm如何使用_哔哩哔哩 Hi&#xff0c;各位好久不见&#xff0c;这里是肆十二&#xff0c;首先在这里给大伙拜年了。 诸位过完年之后估计又要开始为了大作业和毕业设计头疼…...

Blazor OIDC 单点登录授权实例5 - 独立SSR App (net8 webapp ) 端授权

目录: OpenID 与 OAuth2 基础知识Blazor wasm Google 登录Blazor wasm Gitee 码云登录Blazor OIDC 单点登录授权实例1-建立和配置IDS身份验证服务Blazor OIDC 单点登录授权实例2-登录信息组件wasmBlazor OIDC 单点登录授权实例3-服务端管理组件Blazor OIDC 单点登录授权实例4 …...

基于蒙特卡洛的电力系统可靠性分析matlab仿真,对比EDNS和LOLP

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 1.课题概述 电力系统可靠性是指电力系统按可接受的质量标准和所需数量不间断地向电力用户供应电力和电能量的能力的量度&#xff0c;包括充裕度和安全性两个方面。发电系统可靠性是指统一并网的全部发电机…...

Spring boot整合redisson报错

Spring boot整合redisson报错 org.redisson.client.RedisConnectionException: Unable to connect to Redis server: localhost/127.0.0.1:6379 原因 原因是计算机连接不上redis导致的 解决方案 重启redis 在redis文件目录下打开cmd 1.检查redis是否在运行 redis-cli p…...

【AIGC】Stable Diffusion的ControlNet插件

ControlNet 介绍 ControlNet 插件是 Stable Diffusion 中的一个重要组件&#xff0c;用于提供对模型的控制和调整。以下是 ControlNet 插件的主要特点和功能&#xff1a; 模型控制&#xff1a; ControlNet 允许用户对 Stable Diffusion 中的模型进行精细的控制和调整。用户可以…...

【蓝桥杯单片机入门记录】认识单片机

目录 单片机硬件平台 单片机的发展过程 单片机开发板 单片机基础知识 电平 数字电路中只有两种电平&#xff1a;高和低 二进制&#xff08;8421码&#xff09; 十六进制 二进制数的逻辑运算 “与” “或” “异或” 标准C与C51 如何学好单片机 端正学习的态度、培…...

Rust 数据结构与算法:3栈:用栈实现符号匹配

1、符号匹配 如&#xff1a; (56)(78)/(43)、{ { ( [ ] [ ])}}、(ab)(c*d)func() 等各类语句的符号匹配。 这里我们关注的不是数字而是括号&#xff0c;因为括号更改了操作优先级&#xff0c;限定了语言的语义&#xff0c;这是非常重要的。如果括号不完整&#xff0c;那么整个…...

用ESP8266快速实现WIFI红外遥控器(SoC模式)

1&#xff0c;硬件结构图 主要使用了esp8266 wifi模块和红外串口通讯模块。有了红外串口通讯模块&#xff0c;省去了单片机的串口通讯和红外编码程序&#xff0c;大大缩短开发时间。因为红外通讯模块不支持3.3VTTL电平&#xff0c;所以两个模块之间加了一个2路电平转换模块&…...

微服务OAuth 2.1认证授权可行性方案(Spring Security 6)

文章目录 一、背景二、微服务架构介绍三、认证服务器1. 数据库创建2. 新建模块3. 导入依赖和配置4. 安全认证配置类 四、认证服务器测试1. AUTHORIZATION_CODE&#xff08;授权码模式&#xff09;1. 获取授权码2. 获取JWT 2. CLIENT_CREDENTIALS(客户端凭证模式) 五、Gateway1.…...

Maui blazor ios 按设备类型设置是否启用safeArea

需求&#xff0c;新做了个app&#xff0c; 使用的是maui blazor技术&#xff0c;里面用了渐变背景&#xff0c;在默认启用SafeArea情况下&#xff0c;底部背景很突兀 由于现版本maui在SafeArea有点bug&#xff0c;官方教程的<ContentPage SafeAreafalse不生效&#xff0c;于…...

C#系列-使用 Minio 做图片服务器实现图片上传 和下载(13)

1、Minio 服务器下载和安装 要在本地安装和运行 MinIO 服务器&#xff0c;你可以按照以下 步骤进行操作&#xff1a; 1. 访问 MinIO 的官方网站&#xff1a;https://min.io/&#xff0c;然后 点击页面上的”Download”按钮。 2. 在下载页面上&#xff0c;选择适合你操作系统的 …...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql

智慧工地管理云平台系统&#xff0c;智慧工地全套源码&#xff0c;java版智慧工地源码&#xff0c;支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求&#xff0c;提供“平台网络终端”的整体解决方案&#xff0c;提供劳务管理、视频管理、智能监测、绿色施工、安全管…...

【LeetCode】算法详解#6 ---除自身以外数组的乘积

1.题目介绍 给定一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O…...

2025年低延迟业务DDoS防护全攻略:高可用架构与实战方案

一、延迟敏感行业面临的DDoS攻击新挑战 2025年&#xff0c;金融交易、实时竞技游戏、工业物联网等低延迟业务成为DDoS攻击的首要目标。攻击呈现三大特征&#xff1a; AI驱动的自适应攻击&#xff1a;攻击流量模拟真实用户行为&#xff0c;差异率低至0.5%&#xff0c;传统规则引…...

基于stm32F10x 系列微控制器的智能电子琴(附完整项目源码、详细接线及讲解视频)

注&#xff1a;文章末尾网盘链接中自取成品使用演示视频、项目源码、项目文档 所用硬件&#xff1a;STM32F103C8T6、无源蜂鸣器、44矩阵键盘、flash存储模块、OLED显示屏、RGB三色灯、面包板、杜邦线、usb转ttl串口 stm32f103c8t6 面包板 …...

FTXUI::Dom 模块

DOM 模块定义了分层的 FTXUI::Element 树&#xff0c;可用于构建复杂的终端界面&#xff0c;支持响应终端尺寸变化。 namespace ftxui {...// 定义文档 定义布局盒子 Element document vbox({// 设置文本 设置加粗 设置文本颜色text("The window") | bold | color(…...

RLHF vs RLVR:对齐学习中的两种强化方式详解

在语言模型对齐&#xff08;alignment&#xff09;中&#xff0c;强化学习&#xff08;RL&#xff09;是一种重要的策略。而其中两种典型形式——RLHF&#xff08;Reinforcement Learning with Human Feedback&#xff09; 与 RLVR&#xff08;Reinforcement Learning with Ver…...

以太网PHY布局布线指南

1. 简介 对于以太网布局布线遵循以下准则很重要&#xff0c;因为这将有助于减少信号发射&#xff0c;最大程度地减少噪声&#xff0c;确保器件作用&#xff0c;最大程度地减少泄漏并提高信号质量。 2. PHY设计准则 2.1 DRC错误检查 首先检查DRC规则是否设置正确&#xff0c;然…...

Gitlab + Jenkins 实现 CICD

CICD 是持续集成&#xff08;Continuous Integration, CI&#xff09;和持续交付/部署&#xff08;Continuous Delivery/Deployment, CD&#xff09;的缩写&#xff0c;是现代软件开发中的一种自动化流程实践。下面介绍 Web 项目如何在代码提交到 Gitlab 后&#xff0c;自动发布…...

数据库优化实战指南:提升性能的黄金法则

在现代软件系统中&#xff0c;数据库性能直接影响应用的响应速度和用户体验。面对数据量激增、访问压力增大&#xff0c;数据库性能瓶颈经常成为项目痛点。如何科学有效地优化数据库&#xff0c;提升查询效率和系统稳定性&#xff0c;是每位开发与运维人员必备的技能。 本文结…...

spring中的@KafkaListener 注解详解

KafkaListener 是 Spring Kafka 提供的一个核心注解&#xff0c;用于标记一个方法作为 Kafka 消息的消费者。下面是对该注解的详细解析&#xff1a; 基本用法 KafkaListener(topics "myTopic", groupId "myGroup") public void listen(String message)…...