iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >如何使用Go开发并发程序
  • 232
分享到

如何使用Go开发并发程序

2024-04-02 19:04:59 232人浏览 薄情痞子
摘要

这篇文章主要介绍如何使用Go开发并发程序,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!我们都知道计算机的核心为 CPU,它是计算机的运算和控制核心,承载了所有的计算任务。最近半个世纪

这篇文章主要介绍如何使用Go开发并发程序,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!

我们都知道计算机的核心为 CPU,它是计算机的运算和控制核心,承载了所有的计算任务。最近半个世纪以来,由于半导体技术的高速发展,集成电路中晶体管的数量也在大幅度增长,这大大提升了 CPU 的性能。著名的摩尔定律——“集成电路芯片上所集成的电路的数目,每隔18个月就翻一番”,描述的就是该种情形。

过于密集的晶体管虽然提高了 CPU 的处理性能,但也带来了单个芯片发热过高和成本过高的问题,与此同时,受限于材料技术的发展,芯片中晶体管数量密度的增加速度已经放缓。也就是说,程序已经无法简单地依赖硬件的提升而提升运行速度。这时,多核 CPU 的出现让我们看到了提升程序运行速度的另一个方向:将程序的执行过程分为多个可并行或并发执行的步骤,让它们分别在不同的 CPU 核心中同时执行,最后将各部分的执行结果进行合并得到最终结果。

并行和并发是计算机程序执行的常见概念,它们的区别在于:

  • 并行 ,指两个或多个程序在 同一个时刻 执行;

  • 并发 ,指两个或多个程序在 同一个时间段内 执行。

并行执行的程序,无论从宏观还是微观的角度观察,同一时刻内都有多个程序在 CPU 中执行。这就要求 CPU 提供多核计算能力,多个程序被分配到 CPU 的不同的核中被同时执行。

而 并发执行的程序 ,仅需要在宏观角度观察到多个程序在 CPU 中同时执行。即使是单核 CPU 也可以通过分时复用的方式,给多个程序分配一定的执行时间片,让它们在 CPU 上被快速轮换执行,从而在宏观上模拟出多个程序同时执行的效果。但从微观角度来看,这些程序其实是在 CPU 中被串行执行。

Go 的 MPG 线程模型

Go 被认为是一门高性能并发语言,得益于它在原生态支持 协程并发 。这里我们首先了解进程、线程和协程这三者的联系和区别。

在多道程序系统中, 进程 是一个具有独立功能的程序关于某个数据集合的一次动态执行过程,是操作系统进行资源分配和调度的基本单位,是应用程序运行的载体。

而 线程 则是程序执行过程中一个单一的顺序控制流程,是 CPU 调度和分派的基本单位。 线程是比进程更小的独立运行基本单位 ,一个进程中可以拥有一个或者以上的线程,这些线程共享进程所持有的资源,在 CPU 中被调度执行,共同完成进程的执行任务。

linux 系统中,根据资源访问权限的不同,操作系统会把内存空间分为内核空间和用户空间:内核空间的代码能够直接访问计算机的底层资源,如 CPU 资源、I/O 资源等,为用户空间的代码提供计算机底层资源访问能力;用户空间为上层应用程序的活动空间,无法直接访问计算机底层资源,需要借助“系统调用”“库函数”等方式调用内核空间提供的资源。

同样,线程也可以分为内核线程和用户线程。 内核线程 由操作系统管理和调度,是内核调度实体,它能够直接操作计算机底层资源,可以充分利用 CPU 多核并行计算的优势,但是线程切换时需要 CPU 切换到内核态,存在一定的开销,可创建的线程数量也受到操作系统的限制。 用户线程 由用户空间的代码创建、管理和调度,无法被操作系统感知。用户线程的数据保存在用户空间中,切换时无须切换到内核态,切换开销小且高效,可创建的线程数量理论上只与内存大小相关。

协程是一种用户线程,属于轻量级线程。协程的调度,完全由用户空间的代码控制;协程拥有自己的寄存器上下文和栈,并存储在用户空间;协程切换时无须切换到内核态访问内核空间,切换速度极快。但这也给开发人员带来较大的技术挑战:开发人员需要在用户空间处理协程切换时上下文信息的保存和恢复、栈空间大小的管理等问题。

Go 是为数不多在语言层次实现协程并发的语言,它采用了一种特殊的两级线程模型:MPG 线程模型(如下图)。

如何使用Go开发并发程序

MPG 线程模型

  • M,即 Machine,相当于内核线程在 Go 进程中的映射,它与内核线程一一对应,代表真正执行计算的资源。在 M 的生命周期内,它只会与一个内核线程关联。

  • P,即 processor,代表 Go 代码片段执行所需的上下文环境。M 和 P 的结合能够为 G 提供有效的运行环境,它们之间的结合关系不是固定的。P 的最大数量决定了 Go 程序的并发规模,由 runtime.GOMAXPROCS 变量决定。

  • G,即 goroutine,是一种轻量级的用户线程,是对代码片段的封装,拥有执行时的栈、状态和代码片段等信息。

在实际执行过程中,M 和 P 共同为 G 提供有效的运行环境(如下图),多个可执行的 G 顺序挂载在 P 的可执行 G 队列下面,等待调度和执行。当 G 中存在一些 I/O 系统调用阻塞了 M时,P 将会断开与 M 的联系,从调度器空闲 M 队列中获取一个 M 或者创建一个新的 M 组合执行, 保证 P 中可执行 G 队列中其他 G 得到执行,且由于程序中并行执行的 M 数量没变,保证了程序 CPU 的高利用率。

如何使用Go开发并发程序

M 和 P 结合示意图

当 G 中系统调用执行结束返回时,M 会为 G 捕获一个 P 上下文,如果捕获失败,就把 G 放到全局可执行 G 队列等待其他 P 的获取。新创建的 G 会被放置到全局可执行 G 队列中,等待调度器分发到合适的 P 的可执行 G 队列中。M 和 P 结合后,会从 P 的可执行 G 队列中无获取 G 执行。当 P 的可执行 G 队列为空时,P 才会加锁从全局可执行 G 队列获取 G。当全局可执行 G 队列中也没有 G 时,P 会尝试从其他 P 的可执行 G 队列中“剽窃”G 执行。

goroutine 和 channel

并发程序中的多个线程同时在 CPU 执行,由于资源之间的相互依赖和竞态条件,需要一定的并发模型协作不同线程之间的任务执行。Go 中倡导使用 CSP 并发模型 来控制线程之间的任务协作,CSP 倡导使用通信的方式来进行线程之间的内存共享。

Go是通过 goroutine 和 channel 来实现 CSP 并发模型的:

  • goroutine,即协程 ,Go 中的并发实体,是一种轻量级的用户线程,是消息的发送和接收方;

  • channel,即通道 , goroutine 使用通道发送和接收消息。

CSP并发模型类似常用的同步队列,它更加关注消息的传输方式,解耦了发送消息的 goroutine 和接收消息的 goroutine,channel 可以独立创建和存取,在不同的 goroutine 中传递使用。

使用关键字 go 即可使用 goroutine 并发执行代码片段,形式如下:

go expression

而 channel 作为一种引用类型,声明时需要指定传输数据类型,声明形式如下:

var name chan T // 双向 channel var name chan <- T // 只能发送消息的 channel var name T <- chan // 只能接收消息的 channel

其中,T 即为 channel 可传输的数据类型。channel 作为队列,遵循消息先进先出的顺序,同时保证同一时刻只能有一个 goroutine 发送或者接收消息。

使用 channel 发送和接收消息形式如下:

channel <- val // 发送消息 val := <- channel // 接收消息 val, ok := <- channel // 非阻塞接收消息

goroutine 向已经填满信息的 channel 发送信息或从没有数据的 channel 接收信息会阻塞自身。goroutine 接收消息时可以使用非阻塞的方式,无论 channel 中是否存在消息都会立即返回,通过 ok 布尔值判断是否接收成功。

创建一个 channel 需要使用 make 函数对 channel 进行初始化,形式如下所示:

ch := make(chan T, sizeOfChan)

初始化 channel 时可以指定 channel 的长度,表示 channel 最多可以缓存多少条信息。下面我们通过一个简单例子演示 goroutine 和 channel 的使用:

package main import ( "fmt" "time" ) //生产者 func Producer(begin, end int, queue chan<- int) { for i:= begin ; i < end ; i++ { fmt.Println("produce:", i) queue <- i } } //消费者 func Consumer(queue <-chan int) { for val := range queue  { //当前的消费者循环消费 fmt.Println("consume:", val) } } func main() { queue := make(chan int) defer close(queue) for i := 0; i < 3; i++ { go Producer(i * 5, (i+1) * 5, queue) //多个生产者 } go Consumer(queue) //单个消费者 time.Sleep(time.Second) // 避免主 goroutine 结束程序 }

这是一个简单的多生产者和单消费的代码例子,生产 goroutine 将生产的数字通过 channel 发送给消费 goroutine。上述例子中,消费 goroutine 使用 for:range 从 channel 中循环接收消息,只有当相应的 channel 被内置函数 close 后,该循环才会结束。channel 在关闭之后不可以再用于发送消息,但是可以继续用于接收消息,从关闭的 channel 中接收消息或者正在被阻塞的 goroutine 将会接收零值并返回。还有一个需要注意的点是,main 函数由主 goroutine 启动,当主 goroutine 即 main 函数执行结束,整个 Go 程序也会直接执行结束,无论是否存在其他未执行完的 goroutine。

1. select 多路复用

当需要从多个 channel 中接收消息时,可以使用 Go 提供的 select 关键字,它提供类似多路复用的能力,使得 goroutine 可以同时等待多个 channel 的读写操作。select 的形式与 switch 类似,但是要求 case 语句后面必须为 channel 的收发操作,一个简单的例子如下:

package main import ( "fmt" "time" ) func send(ch chan int, begin int )  { // 循环向 channel 发送消息 for i :=begin ; i< begin + 10 ;i++{ ch <- i } } func receive(ch <-chan int)  { val := <- ch fmt.Println("receive:", val) } func main()  { ch2 := make(chan int) ch3 := make(chan int) go send(ch2, 0) go receive(ch3) // 主 goroutine 休眠 1s,保证调度成功 time.Sleep(time.Second) for { select { case val := <- ch2: // 从 ch2 读取数据 fmt.Printf("get value %d from ch2\n", val) case ch3 <- 2 : // 使用 ch3 发送消息 fmt.Println("send value by ch3") case <-time.After(2 * time.Second): // 超时设置 fmt.Println("Time out") return } } }

在上述例子中,我们使用 select 关键字同时从 ch2 中接收数据和使用 ch3 发送数据,输出的一种可能结果为:

get value 0 from ch2 get value 1 from ch2 send value by ch3 receive: 2 get value 2 from ch2 get value 3 from ch2 get value 4 from ch2 get value 5 from ch2 get value 6 from ch2 get value 7 from ch2 get value 8 from ch2 get value 9 from ch2 Time out

由于 ch3 中的消息仅被接收一次,所以仅出现一次“send value by ch3”,后续消息的发送将被阻塞。select 语句分别从 3 个 case 中选取返回的 case 进行处理,当有多个 case 语句同时返回时,select 将会随机选择一个 case 进行处理。如果 select 语句的最后包含 default 语句,该 select 语句将会变为非阻塞型,即当其他所有的 case 语句都被阻塞无法返回时,select 语句将直接执行 default 语句返回结果。在上述例子中,我们在最后的 case 语句使用了 <-time.After(2 * time.Second) 的方式指定了定时返回的 channel,这是一种有效从阻塞的 channel 中超时返回的小技巧。

2. Context 上下文

当需要在多个 goroutine 中传递上下文信息时,可以使用 Context 实现。Context 除了用来传递上下文信息,还可以用于传递终结执行子任务的相关信号,中止多个执行子任务的 goroutine。Context 中提供以下接口:

type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} }
  • Deadline 方法,返回 Context 被取消的时间,也就是完成工作的截止日期;

  • Done,返回一个 channel,这个channel 会在当前工作完成或者上下文被取消之后关闭,多次调用 Done 方法会返回同一个 channel;

  • Err 方法,返回 Context 结束的原因,它只会在 Done 返回的 channel 被关闭时才会返回非空的值,如果 Context 被取消,会返回 Canceled 错误;如果 Context 超时,会返回 DeadlineExceeded 错误。

  • Value 方法,可用于从 Context 中获取传递的键值信息。

WEB 请求的处理过程中,一个请求可能启动多个 goroutine 协同工作,这些 goroutine 之间可能需要共享请求的信息,且当请求被取消或者执行超时时,该请求对应的所有 goroutine 都需要快速结束,释放资源。Context 就是为了解决上述场景而开发的,我们通过下面一个例子来演示:

package main import ( "context" "fmt" "time" ) const DB_ADDRESS  = "db_address" const CALCULATE_VALUE  = "calculate_value" func readDB(ctx context.Context, cost time.Duration)  { fmt.Println("db address is", ctx.Value(DB_ADDRESS)) select { case <- time.After(cost): //  模拟数据库读取 fmt.Println("read data from db") case <-ctx.Done(): fmt.Println(ctx.Err()) // 任务取消的原因 // 一些清理工作 } } func calculate(ctx context.Context, cost time.Duration)  { fmt.Println("calculate value is", ctx.Value(CALCULATE_VALUE)) select { case <- time.After(cost): //  模拟数据计算 fmt.Println("calculate finish") case <-ctx.Done(): fmt.Println(ctx.Err()) // 任务取消的原因 // 一些清理工作 } } func main()  { ctx := context.Background(); // 创建一个空的上下文 // 添加上下文信息 ctx = context.WithValue(ctx, DB_ADDRESS, "localhost:10086") ctx = context.WithValue(ctx, CALCULATE_VALUE, 1234) // 设定子 Context 2s 后执行超时返回 ctx, cancel := context.WithTimeout(ctx, time.Second * 2) defer cancel() // 设定执行时间为 4 s go readDB(ctx, time.Second * 4) go calculate(ctx, time.Second * 4)  // 充分执行 time.Sleep(time.Second * 5) }

在上述例子中,我们模拟了一个请求中同时进行数据库访问和逻辑计算的操作,在请求执行超时时,及时关闭尚未执行结束 goroutine。我们首先通过 context.WithValue 方法为 context 添加上下文信息,Context 在多个 goroutine 中是并发安全的,可以安全地在多个 goroutine 中对 Context 中的上下文数据进行读取。接着使用 context.WithTimeout 方法设定了 Context 的超时时间为 2s,并传递给 readDB 和 calculate 两个 goroutine 执行子任务。在 readDB 和 calculate 方法中,使用 select 语句对 Context 的 Done 通道进行监控。由于我们设定了子 Context 将在 2s 之后超时,所以它将在 2s 之后关闭 Done 通道;然而预设的子任务执行时间为 4s,对应的 case 语句尚未返回,执行被取消,进入到清理工作的 case 语句中,结束掉当前的 goroutine 所执行的任务。预期的输出结果如下:

calculate value is 1234 db address is localhost:10086 context deadline exceeded context deadline exceeded

使用 Context,能够有效地在一组 goroutine 中传递共享值、取消信号、deadline 等信息,及时关闭不需要的 goroutine。

以上是“如何使用Go开发并发程序”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注编程网JavaScript频道!

--结束END--

本文标题: 如何使用Go开发并发程序

本文链接: https://www.lsjlt.com/news/80302.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 如何使用Go开发并发程序
    这篇文章主要介绍如何使用Go开发并发程序,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!我们都知道计算机的核心为 CPU,它是计算机的运算和控制核心,承载了所有的计算任务。最近半个世纪...
    99+
    2024-04-02
  • 如何在 Go 中使用 PATH 打包并发程序?
    Go 语言是一种现代化的编程语言,它在并发编程方面非常出色。而在并发编程中,如何使用 PATH 打包程序是一个非常重要的话题。在本文中,我们将探讨如何在 Go 中使用 PATH 打包并发程序。 什么是 PATH? PATH 是 Go 语言中...
    99+
    2023-10-01
    path 打包 并发
  • 如何使用Go语言开发Websocket应用程序
    使用Go语言开发Websocket应用程序Websocket是一种支持全双工通信的网络协议,它允许服务器主动向客户端发送数据,而不需要客户端先发起请求。Go语言对Websocket的支持非常完善,提供了一个标准库"net/http"中的"g...
    99+
    2023-12-14
    开发 Go语言 websocket
  • GO如何使用Mutex确保并发程序正确性
    这篇“GO如何使用Mutex确保并发程序正确性”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“GO如何使用Mutex确保并发程...
    99+
    2023-07-05
  • Go 中的并发编程:如何使用 PATH 打包并发?
    Go 语言是一门支持并发编程的语言,具有高效和简洁的特性。PATH 是 Go 中的一种并发编程模型,它可以帮助程序员在并发编程过程中更加方便地管理和控制协程的执行。在本文中,我们将探讨如何使用 PATH 打包并发,以及如何编写高效的并发代码...
    99+
    2023-10-01
    path 打包 并发
  • GO语言开发中的并发问题:如何优化程序性能?
    GO语言是一种高效的编程语言,它具有并发能力,可以用于开发高性能的应用程序。但是,在开发过程中,我们可能会遇到一些并发问题,这些问题可能会导致程序性能下降。本文将介绍GO语言开发中的并发问题,以及如何优化程序性能。 一、并发问题 在GO语言...
    99+
    2023-11-04
    开发技术 编程算法 并发
  • 如何使用Go语言和Redis开发高并发系统
    如何使用Go语言和Redis开发高并发系统引言:随着互联网的快速发展,高并发系统的需求也越来越大。在这样的背景下,Go语言和Redis作为高性能的工具,成为了众多开发者的首选。本文将介绍如何使用Go语言和Redis开发高并发系统,包括详细的...
    99+
    2023-10-26
    Go语言 redis 高并发系统
  • 如何使用Go开发技术提高Apache并发性能?
    Go语言是一种快速、高效的编程语言,它在网络编程和并发处理方面非常出色。在本文中,我们将介绍如何使用Go语言开发技术来提高Apache的并发性能。 Apache是一个广泛使用的Web服务器,它的并发性能对于高流量的网站来说至关重要。我们可以...
    99+
    2023-08-25
    开发技术 apache 并发
  • 如何使用Go语言开发大数据应用程序?
    随着大数据时代的到来,越来越多的企业和开发者开始关注如何使用高效的编程语言来处理海量数据。Go语言作为一门高效、简洁、并发的编程语言,越来越受到大数据开发者们的青睐。那么,如何使用Go语言开发大数据应用程序呢?本文将从以下几个方面进行介绍。...
    99+
    2023-08-17
    大数据 开发技术 http
  • 使用Go语言开发高效的并发编程应用
    使用Go语言开发高效的并发编程应用随着互联网的快速发展和计算机性能的不断提升,人们对于软件系统的要求也越来越高。尤其是在网络应用开发中,高并发处理成为了一项重要的技术挑战。而Go语言作为一门强调并发编程的语言,逐渐成为了开发高效并发应用的首...
    99+
    2023-11-20
    Go语言 并发编程 高效应用
  • Go 编程:如何使用 PATH 打包并发?
    在 Go 编程中,PATH 是一个非常重要的概念。它是用于设置环境变量的一个列表,其中包含了一系列路径,这些路径指示了操作系统在哪里查找可执行文件。在本篇文章中,我们将深入探讨如何使用 PATH 打包并发。 在 Go 中,我们可以使用 P...
    99+
    2023-10-01
    path 打包 并发
  • Go开发-使用Goroutine如何控制HTTP请求的并发量
    一、明确需求 我们使用 go 并发调用接口发起 HTTP 请求时,只需要在 func() 前面加上 go 关键字就很容易完成了,就是因为让并发变得如此简单,所以有的时候我们就需要控制一下并发请求的数量。 现在有个需求:本地有一千万条手机号,...
    99+
    2024-04-02
  • 如何使用JCTools实现Java并发程序
    目录概述非阻塞算法依赖JCTools队列队列实现原子队列容量其他数据结构工具性能测试使用JCTools的缺点结论概述 在本文中,我们将介绍JCTools(Java并发工具)库。 简...
    99+
    2024-04-02
  • Laravel 接口开发中,如何使用 Go 实现高并发操作?
    在 Laravel 接口开发中,我们经常需要处理高并发请求。为了满足这种需求,我们可以使用 Go 实现并发操作。本文将介绍如何在 Laravel 中使用 Go 实现高并发操作,并演示一些代码示例。 一、安装 Go 首先,我们需要在本地安装...
    99+
    2023-08-17
    laravel linux 接口
  • go并发利器sync.Once如何使用
    这篇文章主要介绍了go并发利器sync.Once如何使用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇go并发利器sync.Once如何使用文章都会有所收获,下面我们一起来看看吧。1. 简介本文主要介绍 Go ...
    99+
    2023-07-05
  • 如何在 Go 中使用容器和并发来加速程序运行?
    Go 是一种强大的编程语言,它被设计用来构建高效、可扩展和可靠的应用程序。其中,容器和并发是 Go 语言中最重要的特性之一,它们可以帮助我们更快地处理数据和任务。本文将介绍如何在 Go 中使用容器和并发来加速程序运行,并提供一些示例代码帮...
    99+
    2023-07-28
    shell 容器 并发
  • GO语言开发技术:如何应对复杂的程序开发?
    GO语言是一种高效、快速、简单并具有强大功能的开发语言,它在近年来被广泛应用于各种程序开发中。在开发复杂程序时,GO语言的高效和简单性可以让开发人员更加容易地处理程序的各种问题。接下来,我们将介绍一些GO语言开发技术,以帮助开发人员更好地...
    99+
    2023-06-28
    学习笔记 教程 开发技术
  • 如何使用vscode开发微信小程序
    1、安装插件 在vscode安装以下几个插件,再将微信小程序的项目导入即可进行小程序开发。vscode只能编辑代码,效果还需要在微信开发者工具中查看。插件安装完成之后,文件就能和微信小程序运行效果...
    99+
    2023-10-06
    微信小程序 vscode 小程序
  • 小程序如何开发
    这篇文章将为大家详细讲解有关小程序如何开发,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。微信官方简易教程我们先来看看微信官网给出的简易教程。起步基础,注册小程序帐号之后,需要安装开发工具,然后在官方开发工...
    99+
    2023-06-02
  • C++并发编程:如何监控和调试并发程序?
    监控和调试并发程序的关键库和工具:库:thread sanitizer (tsan) 检测数据竞争和死锁std::concurrent_unordered_map 线程安全哈希映射工具:...
    99+
    2024-05-06
    c++ 程序设计 并发访问 并发请求
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作