掌握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之间的数据通讯。

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

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

相关文章

【Docker】集群容器监控和统计 Portainer基本用法

Portainer是一款轻量级的应用&#xff0c;它提供了图形化界面&#xff0c;用川于方便地管理Docker环境&#xff0c;包括单机环境和集群环境。 主要功能&#xff1a;实现集群容器的监控和统计 下载安装 官网&#xff1a;https://www.portainer.io 文档&#xff1a;https://do…

C#分部类的应用:记录学生信息

目录 一、分部类及其用途 二、实例 再发一个分部类的应用&#xff0c;巩固一下。 一、分部类及其用途 C#中的部分类也被称为分部类。 C#中的部分类是一种将类的定义分成多个部分&#xff0c;每个部分都位于自己的文件中&#xff0c;然后在编译时合并在一起的机制。 部分类…

(十)【Jmeter】线程(Threads(Users))之jp@gc - Stepping Thread Group (deprecated)

简述 操作路径如下: 作用:通过逐步增加线程数来模拟用户并发访问。配置:设置This group will start、First,wait for 、Then start、Next , add等参数。使用场景:模拟逐步增长的并发访问,观察应用程序的性能变化。优点:适用于测试应用程序在逐步增加负载下的性能表现。…

stm32——hal库学习笔记(定时器)

这里写目录标题 一、定时器概述&#xff08;了解&#xff09;1.1&#xff0c;软件定时原理1.2&#xff0c;定时器定时原理1.3&#xff0c;STM32定时器分类1.4&#xff0c;STM32定时器特性表1.5&#xff0c;STM32基本、通用、高级定时器的功能整体区别 二、基本定时器&#xff0…

智慧城市与数字孪生:实现城市可持续发展的关键

一、引言 随着全球城市化进程的加速&#xff0c;城市面临着诸多挑战&#xff0c;如资源紧张、环境恶化、交通拥堵等。为了解决这些问题&#xff0c;智慧城市的概念应运而生。智慧城市利用先进的信息通信技术&#xff0c;提升城市治理水平&#xff0c;改善市民的生活质量。而数…

[C#]winform基于opencvsharp结合pairlie算法实现低光图像增强黑暗图片变亮变清晰

【低光图像增强介绍】 在图像处理领域&#xff0c;低光图像增强是一个具有挑战性的任务。由于光线不足&#xff0c;这些图像往往呈现出低对比度、高噪声和细节丢失等问题&#xff0c;严重影响了图像的视觉效果和后续分析的准确性。因此&#xff0c;开发有效的低光图像增强方法…

Redis(03)——发布订阅

基础命令 基于频道 publish channel message&#xff1a;将信号发送到指定的频道pubsub subcommand [argument [argyment]]&#xff1a;查看订阅或发布系统状态subscribe channel [channel]&#xff1a;订阅一个或多个频道的信息unsubscribe [channel [channel]]&#xff1a;退…

SpringBootWeb学习笔记——12万字详细总结!

0. 写在前面 注:这套笔记是根据黑马程序员B站2023-3-21的视频学习的成果,其中省略了前端基础部分、Maven部分和数据库基础部分,详情可见目录。 注注:目前文章内结尾处多幅图片加载不出来,因为图片还存在本地没被传上来,过段时间再改~ 所有的Spring项目都基于Spring Fra…

Vue路由组件练习

Vue 路由组件练习 1. 演示效果 2. 代码分析 2.1. 安装 vue-router 命令&#xff1a;npm i vue-router 应用插件&#xff1a;Vue.use(VueRouter) 2.2. 创建路由文件 在 src 文件夹下&#xff0c;创建router文件夹&#xff0c;并在该文件夹创建index.js文件 2.3. 导入依赖…

解决vscode每次git pull/push都需要输入账号密码

git如何设置用户名 邮箱 密码 //设置用户 git config --global user.name "xxx"//设置邮箱 git config --global user.email "xxxxxx.com"//设置密码 git config --global user.password "xxxxx"解决每次git pull/push操作都需要输入密码 git …

ctfshow-web29~40-WP

web29 if(isset($_GET[c])){$c = $_GET[c];if(!preg_match("/flag/i", $c)){eval($c);}}else{highlight_file(__FILE__); } 首先先system(“ls”);查看一下文件 既然过滤了flag,那我们就fla*的形式进行匹配,结合tac命令输出flag.php文件内容

Collection集合体系(ArrayList,LinekdList,HashSet,LinkedHashSet,TreeSet,Collections)

目录 一.Collection 二.List集合 三.ArrayList集合 四.LinkedList集合 五.Set集合 六.hashSet集合 七.LinkedHashSet集合 八.TreeSet集合 九.集合工具类Collections 集合体系概述 单列集合&#xff1a;Collection代表单列集合&#xff0c;每个元素&#…

2024牛客寒假算法基础集训营4(视频讲解题目)

2024牛客寒假算法基础集训营4&#xff08;视频讲解题目&#xff09; 视频链接ABCDEFG、H&#xff08;下面是hard版本的代码两个都可以过&#xff09; 视频链接 2024牛客寒假算法基础集训营4&#xff08;视频讲解题目&#xff09; A #include<bits/stdc.h> #define en…

Linux(五)__系统管理

介绍 通常&#xff0c; Windows 中使用"任务管理器"主要有 3 个目的&#xff1a; 利用"应用程序"和"进程"标签来査看系统中到底运行了哪些程序和进程&#xff1b;利用"性能"和"用户"标签来判断服务器的健康状态&#xff1…

【c++】vector的增删查改

1.先定义一个类对象vector 为了防止和库里面发生冲突&#xff0c;定义一个命名空间&#xff0c;将类对象放在命名空间 里面 #include<iostream> using namespace std; namespace zjw {class vector {public:private:}; }2.定义变量&#xff0c;需要一个迭代器&#xff…

Python开发户型图编辑器-2D/3D户型图展示

在现代家居设计中&#xff0c;户型图是不可或缺的工具&#xff0c;它为设计师和业主提供了一个直观的展示和规划空间的方式。然而&#xff0c;传统的户型图编辑软件往往复杂难用&#xff0c;限制了设计师的创作灵感。我们为您带来了一款全新的Python开发的户型图编辑器&#xf…

IDEA中创建web项目(配置tomcat,tomcat启动报程序包javax.servlet.http不存在,tomcat控制台乱码问题)

文章目录 一、新建动态web项目1、新建项目2、选择创建动态web项目3、项目命名4、编辑index.jsp 二、配置Tomcat1、新增tomcat服务器配置2、选择服务器类型3、配置服务器参数4、部署项目5、完成配置6、启动运行7、访问web项目 三、tomcat启动报程序包javax.servlet.http不存在四…

数组的左旋和右旋算法

public class Test09 {public static void main(String[] args) {//右旋 数组逆序遍历&#xff0c;将尾部元素&#xff0c;不断交换至头部int[] arr {1,2,3,4,5,6,7,8};for(int i arr.length-1;i>0;i--) { //遍历一次arr[i] arr[i] ^ arr[i-1];arr[i-1] arr[i] ^ arr[i…

【maya 入门笔记】基本视图和拓扑

1. 界面布局 先看基本窗口布局&#xff0c;基本窗口情况如下&#xff1a; 就基本窗口布局的情况来看&#xff0c;某种意义上跟blender更像一点&#xff08;与3ds max相比&#xff09;。 那么有朋友就说了&#xff0c;玛格基&#xff0c;那blender最下面的时间轴哪里去了&…

Java中哪些很容易出现的坑

文章目录 1空指针2小数的计算3包装类型4Java8 Stream5日期格式化 先来一个简单一点&#xff0c;就从空指针开始吧 1空指针 //多级调用空指针userService.getUser("张三").getUserInfo().getUserName(); //例如getUser("张三")、getUserInfo&#xff08;&a…