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

news/发布时间2024/5/15 22:00:32

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

🏷️系列专栏: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之间的数据通讯。

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

如若内容造成侵权/违法违规/事实不符,请联系编程老四网进行投诉反馈email:xxxxxxxx@qq.com,一经查实,立即删除!

相关文章

LabVIEW智能温度监控系统

LabVIEW智能环境监测系统 介绍了一个基于LabVIEW的智能环境监测系统的开发过程。该系统在实时监测和分析环境参数&#xff0c;如温度、湿度、气体浓度等&#xff0c;以提供精确的数据支持&#xff0c;确保环境安全与健康。通过高效的数据处理和友好的用户界面&#xff0c;系统…

pytest 框架自动化测试

随笔记录 目录 1. 安装 2. 安装pytest 相关插件 2.1 准备阶段 2.2 安装 2.3 验证安装成功 3. pytest测试用例的运行方式 3.1 主函数模式 3.1.1 主函数执行指定文件 3.1.2 主函数执行指定模块 3.1.3 主函数执行某个文件中的某个类、方法、函数 3.1.4 主函数执行生…

Mysql Day06

sql优化 插入数据 大批量插入数据 主键顺序插入性能高于乱序插入 load data local infile /root/load_user_100w_sort.sql into table tb_user fields terminated by , lines terminated by \n ; 主键优化 这个黄色的都是一个一个Page 主键乱序插入之后会变成1-3-2&#x…

2.18 C++ day6

思维导图 以下是一个简单的比喻&#xff0c;将多态概念与生活中的实际情况相联系&#xff1a; 比喻&#xff1a;动物园的讲解员和动物表演 想象一下你去了一家动物园&#xff0c;看到了许多不同种类的动物&#xff0c;如狮子、大象、猴子等。现在&#xff0c;动物园里有一位讲…

【内网穿透】如何在小米4A中刷OpenWRT并实现公网访问本地路由器

文章目录 前言1. 安装Python和需要的库2. 使用 OpenWRTInvasion 破解路由器3. 备份当前分区并刷入新的Breed4. 安装cpolar内网穿透4.1 注册账号4.2 下载cpolar客户端4.3 登录cpolar web ui管理界面4.4 创建公网地址 5. 固定公网地址访问 前言 OpenWRT是一个高度模块化、高度自…

Ubuntu本地安装code-server结合内网穿透实现安卓平板远程写代码

文章目录 1.ubuntu本地安装code-server2. 安装cpolar内网穿透3. 创建隧道映射本地端口4. 安卓平板测试访问5.固定域名公网地址6.结语 1.ubuntu本地安装code-server 准备一台虚拟机,Ubuntu或者centos都可以&#xff0c;这里以VMwhere ubuntu系统为例 下载code server服务,浏览器…

算法-3-基本的数据结构

单双链表 1.单链表双链表如何反转 import java.util.ArrayList; import java.util.List;public class Code01_ReverseList {public static class Node {public int value;public Node next;public Node(int data) {value data;}}public static class DoubleNode {public int…

读写分离的利器——MySQL Proxy

0 引言 MySQL Proxy是一个位于客户端和MySQL服务器端之间的程序&#xff0c;通过它可以实现监听和管理客户端与MySQL服务器端之间的通信&#xff0c;最大的作用是实现数据库的读写分离&#xff0c;从而达到负载均衡的目的。 MySQL Proxy的常用用途包括负载平衡、故障分析、查…

Python进阶--爬取美女图片壁纸(基于回车桌面网的爬虫程序)

目录 一、前言 二、爬取下载美女图片 1、抓包分析 a、分析页面 b、明确需求 c、抓包搜寻 d、总结特点 2、编写爬虫代码 a、获取图片页网页源代码 b、提取所有图片的链接和标题 c、下载并保存这组图片 d、 爬取目录页的各种类型美女图片的链接 e、实现翻页 三、各…

《Go 简易速速上手小册》第8章:网络编程(2024 最新版)

文章目录 8.1 HTTP 客户端与服务端编程 - Go 语言的网络灯塔与探航船8.1.1 基础知识讲解服务端编程客户端编程 8.1.2 重点案例&#xff1a;简易博客服务服务端实现客户端实现运行示例 8.1.3 拓展案例 1&#xff1a;增加文章评论功能功能描述服务端实现客户端实现 8.1.4 拓展案例…

PLC远程监控在制药行业的应用

PLC远程监控在制药行业的应用 制药行业是一个需要高度控制和精确性的行业&#xff0c;而PLC远程监控技术正是这种需求的完美解决方案。PLC远程监控技术是指将传感器、执行器和其他设备连接到PLC系统中&#xff0c;并使用网络和远程访问技术实现对设备的远程监控和控制。下面我…

Mysql 触发器

0 引用 MySQL的触发器和存储过程一样&#xff0c;都是嵌入到MySQL的一段程序。触发器是由事件来触发某个操作&#xff0c;这些事件包括INSERT、UPDATAE和DELETE语句。 1 创建触发器 触发器&#xff08;trigger&#xff09;是一个特殊的存储过程&#xff0c;不同的是&#xf…

深度学习之梯度下降算法

梯度下降算法 梯度下降算法数学公式结果 梯度下降算法存在的问题随机梯度下降算法 梯度下降算法 数学公式 这里案例是用梯度下降算法&#xff0c;来计算 y w * x 先计算出梯度&#xff0c;再进行梯度的更新 import numpy as np import matplotlib.pyplot as pltx_data [1.0,…

Apache httpd 换行解析漏洞复现(CVE-2017-15715)

Web页面&#xff1a; 新建一个一句话木马&#xff1a; 0.php <?php system($_GET[0]); ?> 上传木马&#xff0c; burpsuite 抓包。 直接上传是回显 bad file。 我们查看数据包的二进制内容&#xff08;hex&#xff09;&#xff0c;内容是以16进制显示的&#xff0c;…

如何流畅进入Github

前言 以下软件是免费的&#xff0c;放心用 一、进入右边的下载链接https://steampp.net/ 二、点击下载 三、点击接受并下载 四、随便选一个下载链接进行下载 五、软件安装好打开后&#xff0c;找到Github 六、点击全部启用 七、再点击左上角的一键加速 八、这个时候你再进Git…

oauth2 授权码模式 流程说明和接口整理

一、说明 oauth2 授权模式一共有四种&#xff0c;即隐式授权模式、授权码授权模式、密码授权模式和客户端授权模式。 这里仅对授权码授权模式所包含的流程和接口做说明和整理。 具体的概念和源码解读&#xff0c;资料有很多&#xff0c;可以自行去搜索学习。 二、流程说明 假…

开工大吉!龙年第一个赚钱机会,是AI知识付费?人工智能文生视频大模型Sora,颠覆性的革命?

2月16日&#xff0c;OpenAI推出AI大模型Sora。美国知名科技博主马克斯布朗利表示&#xff0c;相比一年前的AI视频工具&#xff0c;Sora有了长足的进步&#xff0c;或将拉响影视行业失业警报。 OpenAI推出AI大模型Sora Sora&#xff0c;OpenAI发布的人工智能文生视频大模型&…

企业计算机服务器中了mkp勒索病毒怎么办?Mkp勒索病毒解密处理

随着网络技术的不断发展&#xff0c;企业的生产运营也加大了步伐&#xff0c;网络为企业的生产运营提供了强有力保障&#xff0c;但网络是一把双刃剑&#xff0c;给企业带来便利的同时也为企业带来了严重的数据威胁。春节期间&#xff0c;云天数据恢复中心接到很多企业的值班人…

AlexNet的出现推动深度学习的巨大发展

尽管AlexNet&#xff08;2012&#xff09;的代码只比LeNet&#xff08;1998&#xff09;多出几行&#xff0c;但学术界花了很多年才接受深度学习这一概念&#xff0c;并应用其出色的实验结果。 AlexNet&#xff08;由Alex Krizhevsky、Ilya Sutskever和Geoffrey Hinton共同设计…

【自然语言处理】seq2seq模型—机器翻译

清华大学驭风计划课程链接 学堂在线 - 精品在线课程学习平台 (xuetangx.com) 代码和报告均为本人自己实现&#xff08;实验满分&#xff09;&#xff0c;只展示主要任务实验结果&#xff0c;如果需要详细的实验报告或者代码可以私聊博主 有任何疑问或者问题&#xff0c;也欢…
推荐文章