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

news2024/11/28 3:44:06

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

🏷️系列专栏: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 main

import "fmt"

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

使用内置 close可以关闭 Channel:

close(ch)

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

close(ch)
ch <- x //panic

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

package main

import "fmt"

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

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

n,ok := <-ch

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

遍历

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

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan int)
	go func(ch chan int) {
		ch <- 10
		ch <- 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 main

import "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 main

import "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 := <-ch1
			if !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 main

import (
	"fmt"
	"time"
)

func main() {

	ch1 := make(chan int)
	ch2 := make(chan int)
	go func(ch1 chan int, ch2 chan int) {
		fmt.Println("向ch1发送数据前")
		<-ch1
		fmt.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 main

import (
	"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("服务器接收到停止信号")
			return
		default:
		}
		//模拟处理请求
		go HandleQuery()
	}
}

func HandleQuery() {
	fmt.Println("处理请求...")
}

Goroutine泄漏

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

package main

import "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.coloradmin.cn/o/1450623.html

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

相关文章

高程 | 数据的共享与保护(c++)

文章目录 &#x1f4da;标识符的作用域与可见性&#x1f407;作用域&#x1f407;可见性 &#x1f4da;对象的生存期&#x1f407;静态生存期&#x1f407;动态生存期 &#x1f4da;类的静态成员&#x1f407;静态数据成员&#x1f407;静态函数成员 &#x1f4da;类的友元&…

什么是位段?位段的作用是什么?他与结构体有什么关系?

目录 1.什么是位段&#xff1f; 2.位段的内存分配 判断当前机器位段的内存分配形式 1.什么是位段&#xff1f; 位段的声明和结构是类似的&#xff0c;有两个不同&#xff1a; 1.位段的成员必须是 int、unsigned int 或signed int或char 。 2.位段的成员名后边有一个冒号和…

相机图像质量研究(13)常见问题总结:光学结构对成像的影响--鬼影

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

STM32 I2C

目录 I2C通信 软件I2C读写MPU6050 I2C通信外设 硬件I2C读写MPU6050 I2C通信 R/W&#xff1a;0写1读 十轴&#xff1a;3轴加速度&#xff0c;3轴角速度&#xff0c;3轴磁场强度和一个气压强度 软件I2C读写MPU6050 MyI2C.c #include "stm32f10x.h" …

【智能家居入门4】(FreeRTOS、MQTT服务器、MQTT协议、微信小程序)

前面已经发了智能家居入门的1、2、3了&#xff0c;在实际开发中一般都会使用到实时操作系统&#xff0c;这里就以FreeRTOS为例子&#xff0c;使用标准库。记录由裸机转到实时操作系统所遇到的问题以及总体流程。相较于裸机&#xff0c;系统实时性强了很多&#xff0c;小程序下发…

[NSSRound#16 Basic]Web

1.RCE但是没有完全RCE 显示md5强比较&#xff0c;然后md5_3随便传 md5_1M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&md5_2M%C9h%FF%0E%E3%5C%20%95r%D4w…

物流快递管理系统

文章目录 物流快递管理系统一、系统演示二、项目介绍三、13000字论文参考四、系统部分页面展示五、部分代码展示六、底部获取项目源码和万字论文参考&#xff08;9.9&#xffe5;带走&#xff09; 物流快递管理系统 一、系统演示 校园物流快递管理系统 二、项目介绍 主要技术…

用HTML5实现动画

用HTML5实现动画 要在HTML5中实现动画&#xff0c;可以使用以下几种方法&#xff1a;CSS动画、使用<canvas>元素和JavaScript来实现动画、使用JavaScript动画库。重点介绍前两种。 一、CSS动画 CSS3 动画&#xff1a;使用CSS3的动画属性和关键帧&#xff08;keyframes&…

备战蓝桥杯---数据结构之好题分享1

最近几天在刷学校的题单时&#xff0c;发现了几道十分巧妙又有启发性的题&#xff0c;借此来记录分享一下。 看题&#xff1a; 从整体上看似乎没有什么规律&#xff0c;于是我们从小地方入手&#xff0c;下面是图解&#xff1a; 因此&#xff0c;我们用栈的数据结构实现即可&a…

模拟算法总结(Java)

目录 模拟算法概述 练习 练习1&#xff1a;替换所有的问号 练习2&#xff1a;提莫攻击 练习3&#xff1a;Z字形变换 模拟算法概述 模拟&#xff1a;根据题目要求的实现过程进行编程模拟&#xff0c;即题目要求什么就实现什么 解决这类题目&#xff0c;需要&#xff1a; 1…

C 语言 devc++ 使用 winsock 实现 windows UDP 局域网发送消息

U参考来源 U 这里移植到windows 上 &#xff0c;使用 devc 开发。 服务端代码 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <winsock2.h>int main() {WORD sockVersion MAKEWORD(2, 2);WSAD…

【嵌入式移植】6、U-Boot源码分析3—make

U-Boot源码分析3—make all 从【嵌入式移植】4、U-Boot源码分析1—Makefile文章中可知执行make命令的时候&#xff0c;没有指定目标则使用默认目标PHONY&#xff0c;PHONY依赖项为_all all scripts_basic outputmakefile scripts dtbs。 all Makefile中第129行指定默认目标PH…

协调尺度:特征缩放在机器学习中的重要作用

目录 一、介绍 二、背景知识 三、了解功能缩放 四、特征缩放方法 五、特征缩放的重要性 六、实际意义 七、代码 八、结论 一、介绍 特征缩放是机器学习和数据分析预处理阶段的关键步骤&#xff0c;在优化各种算法的性能和效率方面起着至关重要的作用。本文深入探讨了特征缩放的…

蓝桥杯每日一题----单调栈和单调队列

单调栈和单调队列 单调栈 单调栈即栈内的元素是单调递减或者单调递增的&#xff0c;我们通过一个题目来理解。 单调栈模板题 题目描述 给出项数为 n 的整数数列 a 1 … a n a_1…a_n a1​…an​。 定义函数 f ( i ) f(i) f(i)代表数列中第 i 个元素之后第一个大于 a i …

安卓游戏开发框架应用场景以及优劣分析

一、引言 在移动游戏开发领域&#xff0c;选择合适的开发框架是项目成功的关键因素之一。特别是对于安卓平台&#xff0c;由于其开放性和庞大的用户基础&#xff0c;不同的游戏开发框架应运而生&#xff0c;旨在帮助开发者高效地构建游戏应用。以下是一些流行的安卓游戏开发框架…

OpenAI全新发布文生视频模型Sora - 现实,不存在了

OpenAI&#xff0c;发他们的文生视频大模型&#xff0c;Sora了。。。。。 而且&#xff0c;是强到&#xff0c;能震惊我一万年的程度。。。 https://openai.com/sora 如果非要用三个词来总结Sora&#xff0c;那就是“60s超长长度”、“单视频多角度镜头”和“世界模型” &am…

五、DataX源码分析、性能参数优化

DataX源码分析 一、总体流程二、程序入口1.datax.py2.com.alibaba.datax.core.Engine.java3.切分的逻辑并发数的确认 3.调度3.1 确定组数和分组算法3.2 数据传输 三、DataX性能优化1.关键参数2.优化&#xff1a;提升每个 channel 的速度3.优化&#xff1a;提升 DataX Job 内 Ch…

SpringBoot3 + Vue3 由浅入深的交互 基础交互教学

说明&#xff1a;这篇文章是适用于已经学过SpringBoot3和Vue3理论知识&#xff0c;但不会具体如何实操的过程的朋友&#xff0c;那么我将手把手从教大家从后端与前端交互的过程教学。 目录 一、创建一个SpringBoot3项目的和Vue3项目并进行配置 1.1后端配置: 1.1.1applicatio…

php基础学习之作用域和静态变量

作用域 变量&#xff08;常量&#xff09;能够被访问的区域&#xff0c;变量可以在常规代码中定义&#xff0c;也可以在函数内部定义 变量的作用域 在 PHP 中作用域严格来说分为两种&#xff0c;但是 PHP内部还定义一些在严格意义之外的一种&#xff0c;所以总共算三种—— 局部…

紫微斗数双星组合:廉贞天相在子午

文章目录 前言内容总结 前言 紫微斗数双星组合&#xff1a;廉贞天相在子午 内容 紫微斗数双星组合&#xff1a;廉贞天相在子午 性格分析 廉贞星最喜天相星同度来制其恶&#xff0c;因天相星之水可剋制廉贞星之火。廉贞星最喜天相星同度来制其恶&#xff0c;使廉贞星变为较温…