go_并发编程

go并发编程

  • 一、 并发介绍
      • 1,进程和线程
      • 2,并发和并行
      • 3,协程和线程
      • 4,goroutine
  • 二、 Goroutine
    • 1,使用goroutine
      • 1)启动单个goroutine
      • 2)启动多个goroutine
    • 2,goroutine与线程
    • 3,goroutine调度
  • 三、runtime包
    • 1,runtime.Gosched()
    • 2,runtime.Goexit() 退出当时协程
    • 3, runtime.GOMAXPROCS
    • 4,将任务分配到不同的CPU逻辑核心上实现并行
    • 5,Go语言中的操作系统线程和goroutine的关系:
  • 四、channel
    • 1,CSP模型
    • 2,channel 类型
    • 3,创建channel
    • 4,channel 操作
      • 1)发送
      • 2)接收
      • 3)关闭
    • 5,无缓冲通道
    • 6,有缓冲通道
    • 7,从通道中遍历获取值
    • 8,单向通道
    • 8,通道总结

一、 并发介绍

1,进程和线程

  • A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。
  • B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。
  • C.一个进程可以创建和撤销多个线程;同一个进程中的多个线程之间可以并发执行

2,并发和并行

  • A. 多线程程序在一个核的cpu上运行,就是并发。
  • B. 多线程程序在多个核的cpu上运行,就是并行。

并发:
在这里插入图片描述
并行:
在这里插入图片描述

3,协程和线程

  • 协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的。
  • 线程:一个线程上可以跑多个协程,协程是轻量级的线程。

4,goroutine

  • goroutine 只是由官方实现的超级"线程池"。
    • 每个实力4~5KB的栈内存占用和由于实现机制而大幅减少的创建和销毁开销是go高并发的根本原因
  • goroutine 奉行通过通信来共享内存,而不是共享内存来通信。

二、 Goroutine

Go语言引入了goroutine机制,简化了并发编程。程序员只需定义任务函数,通过开启goroutine实现并发执行,而无需自己管理线程池、任务调度和上下文切换。
Go运行时负责智能分配任务到CPU,将复杂性隐藏在底层。
这使得Go成为现代化编程语言,使并发编程更加简单和高效。

1,使用goroutine

  • 在函数,或匿名函数 前面添加 go 关键词。
  • 一个goroutine必定对应一个函数,可以创建多个goroutine去执行相同的函数。

1)启动单个goroutine

func hello() {fmt.Println("Hello Goroutine!")
}
func main() {go  hello()fmt.Println("main goroutine done!")
}//但打印没有 ,Hello Goroutine! ,因为main 生命周期结束,goroutine 还没启动//让main 等等 goroutine 粗暴方法:func main() {go hello() // 启动另外一个goroutine去执行hello函数fmt.Println("main goroutine done!")time.Sleep(time.Second)
}

2)启动多个goroutine

  • 使用了sync.WaitGroup来实现goroutine的同步
var wg sync.WaitGroupfunc hello(i int) {defer wg.Done() // goroutine结束就登记-1fmt.Println("Hello Goroutine!", i)
}
func main() {for i := 0; i < 10; i++ {wg.Add(1) // 启动一个goroutine就登记+1go hello(i)}wg.Wait() // 等待所有登记的goroutine都结束
}打印出来,并不是顺序,因为这是因为10个goroutine是并发执行的,而goroutine的调度是随机的。

2,goroutine与线程

  • 可增长栈
    OS线程(操作系统线程)一般都有固定的栈内存(通常为2MB),一个goroutine的栈在其生命周期开始时只有很小的栈(典型情况下2KB),goroutine的栈不是固定的,他可以按需增大和缩小,goroutine的栈大小限制可以达到1GB,虽然极少会用到这个大。所以在Go语言中一次创建十万左右的goroutine也是可以的。

3,goroutine调度

Go语言的运行时(runtime)引入了GPM模型来实现并发调度,与传统操作系统调度不同。

  • G(goroutine): G是goroutine的缩写,代表一个任务单元。它存储了该任务的信息,以及与所在P(处理器)的关联。

  • P(处理器): P管理一组goroutine队列,包含当前goroutine的运行上下文。P负责调度自己的队列,比如暂停耗时长的任务、切换到其他任务。当P队列为空,它会从全局队列取任务,甚至从其他P队列抢占任务。

  • M(机器): M是Go运行时对操作系统内核线程的虚拟。通常是一一对应的关系,每个M执行一个goroutine。当一个G长时间阻塞在一个M上,会创建新的M,将其他G挂载在新M上。旧M释放后,用于回收资源。

  • GOMAXPROCS: 用于设定P的个数,控制并发度,但不会过多地增加P和M,以避免频繁切换的开销。

Go语言与其他语言不同之处在于,它在运行时实现了自己的调度器,使用m:n调度技术。这意味着goroutine的调度发生在用户态,避免了内核态与用户态的频繁切换,包括内存分配与释放都在用户态维护,性能开销较小。此外,Go语言充分利用多核硬件资源,将多个goroutine均匀分配在物理线程上,加上goroutine的轻量特性,保证了高效的并发调度性能。

三、runtime包

1,runtime.Gosched()

一种协作式多任务切换的方式,让正在运行的 goroutine 暂时停下来,让其他等待执行的 goroutine 有机会运行。

package mainimport ("fmt""runtime"
)func main() {go func(s string) {for i := 0; i < 5; i++ {fmt.Println(s)}}("world")// 主程for i := 0; i < 2; i++ {//切换 再次分配任务runtime.Gosched()fmt.Println("hello")}
}

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 {}
}

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逻辑核心数。

4,将任务分配到不同的CPU逻辑核心上实现并行

  • 单核心
package mainimport ("fmt""runtime""time"
)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)
}输出,看出是执行完一个goroutine ,再执行另一个
B: 1
B: 2
B: 3
B: 4
B: 5
B: 6
B: 7
B: 8
B: 9
A: 1
A: 2
A: 3
A: 4
A: 5
A: 6
A: 7
A: 8
A: 9
  • 多核心
package mainimport ("fmt""runtime""time"
)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)
}输出,看出是并发执行
B: 1
B: 2
B: 3
B: 4
B: 5
B: 6
B: 7
B: 8
A: 1
A: 2
A: 3
A: 4
A: 5
A: 6
A: 7
A: 8
A: 9
B: 9

5,Go语言中的操作系统线程和goroutine的关系:

  • 1.一个操作系统线程对应用户态多个goroutine。
  • 2.go程序可以同时使用多个操作系统线程。
  • 3.goroutine和OS线程是多对多的关系,即m:n

四、channel

1,CSP模型

并发执行函数的目的是让多个任务同时进行,但仅仅并发执行函数是不够的,因为这些函数可能需要相互交换数据。在并发环境中,共享内存虽然可以用于数据交换,但容易引发竞态问题,而使用互斥量会影响性能。

Go语言采用了CSP(Communicating Sequential Processes)并发模型,强调通过通信来共享数据,而不是通过共享数据来进行通信。这种方式更加安全且高效。

  • 关键点:

    • CSP模型: Go语言采用了CSP模型,强调通过通信来实现协程(goroutine)间的数据交换,而不是直接共享内存。

    • 通道(channel): 通道是用于协程间通信的一种机制,类似于一个队列,保证了数据的顺序性。通过在通道中发送和接收数据,协程可以安全地进行交互。

    • 通道的特点: 每个通道都有特定的元素类型,通道的操作遵循先进先出原则。通过通道的发送和接收操作,协程之间可以安全地进行数据交换,避免了竞态问题。

    • 并发优势: 通过通道,Go语言实现了安全且高效的并发编程,允许协程在不同任务之间进行数据交换,而不需要显式地使用互斥量进行加锁。

通过使用通道,Go语言的并发模型强调了协程之间通过通信共享数据,而不是通过共享数据来进行通信,从而避免了许多传统并发模型中常见的问题。这使得并发编程更加安全、简洁和高效。

2,channel 类型

  • channel 是一种类型,引用类型
声明格式:var 变量 chan 元素类型
例如:var ch1 chan int   // 声明一个传递整型的通道var ch2 chan bool  // 声明一个传递布尔型的通道var ch3 chan []int // 声明一个传递int切片的通道

3,创建channel

通道是引用类型,通道类型的空值是nil。var ch chan int
fmt.Println(ch) // <nil>声明的通道后需要使用make函数初始化之后才能使用。创建channel的格式如下:make(chan 元素类型, [缓冲大小])		// 缓冲大小可选例如:
ch4 := make(chan int)
ch5 := make(chan bool)
ch6 := make(chan []int)

4,channel 操作

1)发送

ch <- 10 // 把10发送到ch中

2)接收

x := <- ch // 从ch中接收值并赋值给变量x
<-ch       // 从ch中接收值,忽略结果

3)关闭

  close(ch)
  • 只有在通知接收方goroutine所有的数据都发送完毕的时候才需要关闭通道。
  • 通道是可以被垃圾回收机制回收的,它和关闭文件是不一样的,在结束操作之后关闭文件是必须要做的,但关闭通道不是必须的。

注意:

	1.对一个关闭的通道再发送值就会导致panic。2.对一个关闭的通道进行接收会一直获取值直到通道为空。3.对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。4.关闭一个已经关闭的通道会导致panic。

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
  • 无缓冲的通道必须有接收才能发送。
  • 上面的代码会阻塞在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同步化。因此,无缓冲通道也被称为同步通道。

6,有缓冲通道

func main() {ch := make(chan int, 1) // 创建一个容量为1的有缓冲区通道ch <- 10fmt.Println("发送成功")
}
  • 只要通道的容量大于零,那么该通道就是有缓冲的通道,通道的容量表示通道中能存放元素的数量。
  • 可以使用内置的len函数获取通道内元素的数量,使用cap函数获取通道的容量。

7,从通道中遍历获取值

package mainimport "fmt"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中接收值,发送给ch2go func() {for {i, ok := <-ch1if !ok {break}ch2 <- i * i}close(ch2)}()// 在主goroutine 打印ch2for i := range ch2 {fmt.Println("ch2:", i)}
}
  • 能从关闭通道中获取值
  • 通过遍历获取通道中(关闭的通道也行)的值

8,单向通道

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是一个只能接收的通道,可以接收但是不能发送。

8,通道总结

在这里插入图片描述

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

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

相关文章

sdk manager (ubuntu20.4) 安装

1、首先下载sdk manager 1.9.3 下载链接 https://www.baidu.com/link?urlVXJhUqxxhS3eFK3bOPTzi5LFl6ybeW3JwDY1CwANaPf1gvO3IxQKzY547NIe53x1blJxnAXg7FTRTvs-cnfnVa&wd&eqida22baa7b0004ca980000000664e2d426 当然要登录自己的账号才能成功下载&#xff0c;下载对应…

用于智能图像处理的计算机视觉和 NLP

莫斯科&#xff0c;神秘之城...&#xff08;这张照片由伊戈尔沙巴林提供&#xff09; 一、说明 如今&#xff0c;每个拥有智能手机的人都可能成为摄影师。因此&#xff0c;每天都有大量新照片出现在社交媒体、网站、博客和个人照片库中。尽管拍照的过程可能非常令人兴奋&#x…

MyBaits注解开发

1、注解开发介绍 在过去使用框架开发项目&#xff0c;基本都采用xml作为框架的核心配置文件存在&#xff0c;但是这种方式开发效率还是比较地下、不灵活。 现在企业开发为了能够更快的提高开发效率&#xff0c;必然会使用企业级框架进行项目开发&#xff0c;而现在主流的框架…

docker 安装oracle19c linux命令执行sql

docker安装oracle # 下载镜像 19.3.0.0.0 docker pull registry.cn-hangzhou.aliyuncs.com/laowu/oracle:19c # 创建文件 mkdir -p /home/mymount/oracle19c/oradata # 授权&#xff0c;不授权会导致后面安装失败 chmod 777 /home/mymount/oracle19c/oradatadocker run -d \ …

还不知道怎么提示LLM?ChatGPT提示入门

文章目录 简介&#xff1a;什么是人工智能&#xff1f;什么是提示过程&#xff1f;为什么会出现这样的差异&#xff1f; 为什么需要提示过程&#xff1f;1) 文章摘要2) 数学问题求解 如何进行提示过程&#xff1f;角色提示&#xff1a;多范例提示&#xff1a;无范例提示单范例提…

Redis-设置密码linux服务器

操作步骤 打开Redis的配置文件&#xff0c;通常位于 /etc/redis/redis.conf。在配置文件中找到 #requirepass 或 requirepass 的行&#xff0c;如果存在的话&#xff0c;取消行首的注释符号 #。将密码设置为你想要的值&#xff0c;例如 requirepass YourPassword。确保将 Your…

Gitlab 安装全流程

Version&#xff1a;gitlab-ce:16.2.4-ce.0 简介 Gitlab 是一个开源的 Git 代码仓库系统&#xff0c;可以实现自托管的 Github 项目&#xff0c;即用于构建私有的代码托管平台和项目管理系统。系统基于 Ruby on Rails 开发&#xff0c;速度快、安全稳定。它拥有与 Github 类似…

Android AppCompatActivity标题栏操作

使用 AndroidStudio 新建的工程默认用 AppCompatActivity &#xff0c;是带标题栏的。 记录下 修改标题栏名称 和 隐藏标题栏 的方法。 修改标题栏名称 Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R…

龙讯旷腾PWmat已部署至曙光智算平台

编者荐语&#xff1a; 近期&#xff0c;龙讯旷腾核心产品PWmat已成功部署至曙光智算AC.sugon.com平台&#xff0c;可为用户提供包括分子建模、第一性原理计算、数据可视化等在内的完备的超级计算云服务&#xff0c;让大家能够轻松上手具有完全自主知识产权的大尺度高性能材料计…

详解Spring的循环依赖问题、三级缓存解决方案源码分析

0、基础&#xff1a;Bean的生命周期 在Spring中&#xff0c;由于IOC的控制反转&#xff0c;创建对象不再是简单的new出来&#xff0c;而是交给Spring去创建&#xff0c;会经历一系列Bean的生命周期才创建出相应的对象。而循环依赖问题也是由Bean的生命周期过程导致的问题&#…

Hyperf 如何做到用两个端口 9501/9502 都能连接 Websocket 服务以及多 Worker 协作实现聊天室功能

为何 Hyperf 能够在两个端口上监听 WebSocket 连接&#xff1f; 源码角度来看&#xff0c;在配置了多个 Servers 时&#xff0c;实际上&#xff0c;只启动了一个 Server 注&#xff1a;我之前接触的代码都是启动一个服务绑定一个端口&#xff0c;之前也看过 swoole 扩展的文档…

SpringBoot 配置优先级

一般而言&#xff0c;SpringBoot支持配置文件进行配置&#xff0c;即在resources下的application.properties或application.yml。 关于配置优先级而言&#xff0c; application.properties>application.yml>application.yaml 另外JAVA程序程序还支持java系统配置和命令行…

PHP实现轻量级WEB服务器接收HTTP提交的RFID刷卡信息并回应驱动读卡器显示播报语音

本示例使用的读卡器&#xff1a;RFID网络WIFI无线TCP/UDP/HTTP可编程二次开发读卡器POE供电语音-淘宝网 (taobao.com) <?php mb_http_output(utf-8); $port88; $socket socket_create(AF_INET, SOCK_STREAM, SOL_TCP); $bool socket_bind($socket, "0.0.0.0",…

html表格中加入斜线,使用css给table表格表头单元格添加斜线

背景&#xff1a;业务给了90张word电子表格&#xff0c;需要用html设计出来。 如图所示&#xff0c;红色区域的下斜线如何实现&#xff1f; 先说结论&#xff1a;html中table没有直接的斜线表头标签&#xff0c;但结合css、svg之类的可以实现。 #lineTd{ background:#FFFFFF u…

卷积神经网络——上篇【深度学习】【PyTorch】【d2l】

文章目录 5、卷积神经网络5.1、卷积5.1.1、理论部分5.1.2、代码实现5.1.3、边缘检测 5.2、填充和步幅5.2.1、理论部分5.2.2、代码实现 5.3、多输入多输出通道5.3.1、理论部分5.3.2、代码实现 5.4、池化层 | 汇聚层5.4.1、理论部分5.4.2、代码实现 5、卷积神经网络 5.1、卷积 …

Elasticsearch学习

1、什么是Elasticsearch? Elaticsearch&#xff0c;简称为 ES&#xff0c; ES 是一个开源的高扩展的分布式全文搜索引擎&#xff0c; 是整个 ElasticStack 技术栈的核心。可以帮助我们从海量数据中快速找到需要的内容。 elasticsearch结合kibana、Logstash、Beats&#xff0…

Hope.money:新兴DeFi项目如何重新定义稳定币生态的未来?

联储加息导致金融市场紧缩&#xff0c;Terra、3AC、FTX等知名中心化机构未能妥善应对而暴雷&#xff0c;并重创了整个加密货币市场&#xff0c;导致参与者损失惨重。这些事件揭示了中心化机构的局限&#xff0c;投资者对其资产掌控权的担忧愈发强烈。 自2018年首个DeFi协议Com…

简单介绍 CPU 的工作原理

内部架构 CPU 的根本任务就是执行指令&#xff0c;对计算机来说最终都是一串由 0 和 1 组成的序列。CPU 从逻辑上可以划分成 3 个模块&#xff0c;分别是控制单元、运算单元和存储单元 。其内部架构如下&#xff1a; 【1】控制单元 控制单元是整个CPU的指挥控制中心&#xff…

2023年Java核心技术面试第二篇(篇篇万字精讲)

目录 四. 强引用&#xff0c;软引用&#xff0c;弱引用&#xff0c;幻象引用之间的区别&#xff1f; 1.前言 2.强引用 2.1 强引用赋值为null 2.2 超过了引用的作用域 2.2.1 描述&#xff1a; 2.2.2 作用域内&#xff1a; 2.2.3 不在作用域内: 3. 软引用&#xff08;SoftRefere…

华为云classroom赋能--Toolkit系列插件DevSecOps助力开发者提速

一、前言 DevOps的概念想必大家都不陌生&#xff0c;它是一组过程、方法与系统的统称&#xff0c;通过它可以对交付速率、协作效率、部署频率速率、质量、安全和可靠性等进行提升改善。相比传统的软件开发模式&#xff0c;它是一种工作方式和文化的转变&#xff0c;把开发者和…