go_并发编程(1)

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.rhkb.cn/news/95155.html

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

相关文章

通过 HttpClient 发送请求

文章目录 1. 引入 maven 依赖2. 发送 GET 方式的请求3. 发送 POST 方式的请求 1. 引入 maven 依赖 <dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId> </dependency>2. 发送 GET 方式的请求…

C++类型转换

目录 隐式类型转换 转型方向 整型提升和截断 显示转换的种类 C风格的强制类型转换 C风格的函数样式转换 隐式类型转换 转型方向 隐式类型转换&#xff0c;又称自动转换、算数转换&#xff0c;是由编译器自动完成的类型转换称为隐式类型转换。自动转换的原则为&#xff…

Leaflet入门,Leaflet如何实现vue双向绑定数据添加到图片标记物到地图上,动态根据vue数据更新到地图上以及鼠标经过标记物显示提示框

前言 本章使用Leaflet的vue2-leaflet或者vue-leaflet插件方式实现vue数据绑定地图数据,实现地图标记物与vue数据的双向联动更新,以及鼠标经过标记物显示提示框功能。 实现效果演示 vue如何使用Leaflet vue2如何使用:《Leaflet入门,如何使用vue2-leaflet实现vue2双向绑定…

GaussDB数据库SQL系列-子查询

目录 一、前言 二、GaussDB SQL子查询表达式 1、EXISTS/NOT EXISTS 2、IN/NOT IN 3、ANY/SOME 4、ALL 三、GaussDB SQL子查询实验示例 1、创建实验表 2、EXISTS/NOT EXISTS示例 3、IN/NOT IN 示例 4、ANY/SOME 示例 5、ALL示例 四、注意事项及建议 五、小结 一、…

msvcp110.dll是什么意思,msvcp110.dll丢失的解决方法

装好软件或游戏之后&#xff0c;一打开就跳出各种报错信息的情况小伙伴一定见过&#xff0c;其中缺少各种msvcp110.dll文件最常见。小伙伴们一定奇怪&#xff0c;用得好好的电脑&#xff0c;怎么会缺文件呢&#xff1f;为啥其他游戏/应用就没事呢&#xff1f;其实这些“丢失”的…

LVS负载均衡群集部署(LVS-NAT模型实例)

一、集群 1.1集群的含义 Cluster&#xff0c;集群、群集,为解决某个特定问题将多台计算机组合起来形成的单个系统。 由多台主机构成&#xff0c;但对外只表现为一个整体。 1.2群集的三种类型 1.2.1负载均衡群集 LB&#xff1a; Load Balancing&#xff0c;负载均衡&#x…

国产化系统中遇到的视频花屏、卡顿以及延迟问题的记录与总结

目录 1、国产化系统概述 1.1、国产化操作系统与国产化CPU 1.2、国产化服务器操作系统 1.3、当前国产化系统的主流配置 2、视频解码花屏与卡顿问题 2.1、视频解码花屏 2.2、视频解码卡顿 2.3、关于I帧和P帧的说明 3、国产显卡处理速度慢导致图像卡顿问题 3.1、视频延…

【C++类和对象】类有哪些默认成员函数呢?(下)

文章目录 一、类的6个默认成员函数二、日期类的实现2.1 运算符重载部分2.2 日期之间的运算2.3 整体代码1.Date.h部分2. Date.cpp部分 三. const成员函数四. 取地址及const取地址操作符重载扩展内容 总结 ヾ(๑╹◡╹)&#xff89;" 人总要为过去的懒惰而付出代价ヾ(๑╹◡…

【Rust】Rust学习 第十二章一个 I/O 项目:构建一个命令行程序

本章既是一个目前所学的很多技能的概括&#xff0c;也是一个更多标准库功能的探索。我们将构建一个与文件和命令行输入/输出交互的命令行工具来练习现在一些你已经掌握的 Rust 技能。 Rust 的运行速度、安全性、单二进制文件输出和跨平台支持使其成为创建命令行程序的绝佳选择…

2023上半年京东手机行业品牌销售排行榜(京东数据平台)

后疫情时代&#xff0c;不少行业都迎来消费复苏&#xff0c;我国智能手机市场在今年上半年也实现温和的复苏&#xff0c;手机市场的出货量回暖。 根据鲸参谋平台的数据显示&#xff0c;2023年上半年&#xff0c;京东平台上手机的销量为2830万&#xff0c;环比增长约4%&#xf…

sql中union all、union、intersect、minus的区别图解,测试

相关文章 sql 的 join、left join、full join的区别图解总结&#xff0c;测试&#xff0c;注意事项 1.结论示意图 对于intersect、minus&#xff0c;oracle支持&#xff0c;mysql不支持&#xff0c;可以变通&#xff08;in或exists&#xff09;实现 2.创建表和数据 -- 建表…

Android Socket使用TCP协议实现手机投屏

本节主要通过实战来了解Socket在TCP/IP协议中充当的是一个什么角色&#xff0c;有什么作用。通过Socket使用TCP协议实现局域网内手机A充当服务端&#xff0c;手机B充当客户端&#xff0c;手机B连接手机A&#xff0c;手机A获取屏幕数据转化为Bitmap&#xff0c;通过Socket传递个…

Linux——KVM虚拟化

目录标题 虚拟化技术虚拟化技术发展案例KVM简介KVM架构及原理KVM原理KVM虚拟化架构/三种模式虚拟化前、虚拟化后对比KVM盖中盖套娃实验 虚拟化技术 通过虚拟化技术将一台计算机虚拟为多台逻辑计算机&#xff0c;在一台计算机上同时运行多个逻辑计算机&#xff0c;同时每个逻辑…

ValueError: check_hostname requires server_hostname

ValueError: check_hostname requires server_hostname 可能是代理的问题&#xff0c;在pip下载库的时候如果之前电脑连过梯子&#xff0c;就会出现。 这个时候就算关掉梯子也无法解决这个错误。 下面是解决方法 右键网络&#xff0c;打开设置&#xff0c;代理 点击手动设置代…

excel 之 VBA

1、excel和VBA 高效办公&#xff0c;把重复性的工作写成VBA代码&#xff08;VB代码的衍生物&#xff0c;语法和VBA相同&#xff09;。 首先打开开发工具模式&#xff0c;如果没有选显卡&#xff0c;需要手动打开 打开程序编辑界面 快捷键 altF11一般操作 程序调试&#xf…

hive--给表名和字段加注释

1.建表添加注释 CREATE EXTERNAL TABLE test(loc_province string comment 省份,loc_city string comment 城市,loc_district string comment 区,loc_street string comment 街道,)COMMENT 每日数据处理后的表 PARTITIONED BY (par_dt string) ROW FORMAT SERDEorg.apache.had…

《离散数学及其应用(原书第8版)》ISBN978-7-111-63687-8 第11章 11.1.3 树的性质 节 第664页的例9说明

《离散数学及其应用&#xff08;原书第8版&#xff09;》ISBN978-7-111-63687-8 第11章 11.1.3 树的性质 节 第664页的定理3的引申 定理3 带有i个内点的m叉树含有nmi1个顶点 见本人博文 内点定义不同的讨论 如果对于一个m叉正则树&#xff0c;即任意分支节点的儿子恰好有m个&am…

Hlang社区-前端社区宣传首页实现

文章目录 前言页面结构固定钉头部轮播JS特效完整代码总结前言 这里的话,博主其实也是今年参与考研的大军之一,所以的话,是抽空去完成这个项目的,当然这个项目的肯定是可以在较短的时间内完成的。 那么废话不多说,昨天也是干到1点多,把这个首页写出来了。先看看看效果吧:…

Maven之JDK编译问题

IDEA Maven 默认使用 JDK 1.5 编译问题 IDEA 在「调用」maven 时&#xff0c;IDEA 默认都会采用 JDK 1.5 编译&#xff0c;不管你安装的 JDK 版本是 JDK 7 还是 JDK 8 或者更高。这样一来非常不方便&#xff0c;尤其是时不时使用 JDK 7/8 的新特性时。如果使用新特性&#xff…

opencv-python使用鼠标点击图片显示该点坐标和像素值IPM逆透视变换车道线二值化处理

OpenCV的鼠标操作 实现获取像素点的功能主要基于OpenCV的内置函数cv2.setMouseCallback()&#xff0c;即鼠标事件回调 setMouseCallback(winname, onMouse,userdata0) winname: 接收鼠标事件的窗口名称 onMouse: 处理鼠标事件的回调函数指针 userdata: 传给回调函数的用户数据…