返回顶部
首页 > 资讯 > 精选 >go语言中的协程实例分析
  • 605
分享到

go语言中的协程实例分析

2023-07-02 19:07:49 605人浏览 八月长安
摘要

这篇文章主要讲解了“Go语言中的协程实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“go语言中的协程实例分析”吧!协程的特点该任务的业务代码主动要求切换,即主动让出执行权限发生了io,

这篇文章主要讲解了“Go语言中的协程实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“go语言中的协程实例分析”吧!

协程的特点

该任务的业务代码主动要求切换,即主动让出执行权限

发生了io,导致执行阻塞(使用channel让协程阻塞)

线程本质的不同

C#、java中我们执行多个线程,是通过时间片切换来进行的,要知道进行切换,程序需要保存上下文等信息,是比较消耗性能的

GO语言中的协程,没有上面这种切换,一定是通过协程主动放出权限,不是被动的。

例如:

C# 中创建两个线程

go语言中的协程实例分析

可以看到1和2是交替执行的

go语言中的协程实例分析

Go语言中用协程实现一下

runtime.GOMAXPROCS(1)

go语言中的协程实例分析

go语言中的协程实例分析

这个结果就是 执行了1 在执行2

上述两种方式来进行协程的切换

  • 该任务的业务代码主动要求切换,即主动让出执行权限

go语言中的协程实例分析

  • 通过channel进行阻塞

go语言中的协程实例分析

执行的结果一样

go语言中的协程实例分析

后面又继续执行1了

一、Goroutine的并行

package mainimport (  "fmt"  "runtime"  "sync")func main() {  runtime.GOMAXPROCS(1)  var wg sync.WaitGroup  wg.Add(3)  fmt.Println("Start Goroutines")  go func() {    defer wg.Done()    for i:=1;i<=10;i++{      fmt.Print("1")    }  }()  go func() {    defer wg.Done()    for i:=1;i<=10;i++ {            fmt.Print("2")    }  }()  go func() {    defer wg.Done()    for i:=1;i<=10;i++ {           fmt.Print("3")    }  }()  fmt.Println("等待执行结束")  wg.Wait()}

运行结果

Start Goroutines

等待执行结束

333333333311111111112222222222

(1)runtime.GOMAXPROCS(1) 的作用是什么?

runtime包的GOMAXPROCS 函数。这个函数允许程序更改调度器可以使用的逻辑处理器的数量。如果不想在代码里做这个调用,也可以通过修改和这个函数名字一样的环境变量的值来更改逻辑处理器的数量。

(2)为什么先输出3后输出1和2?

调度器源码中有体现: 有一个队列,还有一个优先执行&mdash;&mdash;即最后一个, 在只有一个逻辑处理器的情况下,先执行优先的那个,再顺序执行队列中的。不过经过各种实验发现这个执行顺序其实是变的,貌似纠结这个顺序也没有什么意思。

(3)WaitGroup 是什么意思?

WaitGroup 是一个计数信号量,可以用来记录并维护运行的goroutine。如果WaitGroup的值大于0,Wait 方法就会阻塞。为了减小WaitGroup 的值并最终释放main 函数,使用defer 声明在函数退出时

调用Done 方法。(defer 有点像C#当中的fianlly)

补充:调度算法中,如果多个goroutine中某个执行过长,此时会将其停止让给其他goroutine继续执行,待到其他都执行完成,在将其进行执行。如下图:G4和G5就进行了切换

go语言中的协程实例分析

如下代码可以验证上面的问题:

package mainimport (    "fmt"    "runtime"    "sync")var wg sync.WaitGroupfunc main() {    runtime.GOMAXPROCS(1)    wg.Add(2)    fmt.Println("Create Goroutines")    go printPrime("A")    go printPrime("B")    fmt.Println("Waiting To Finish")    wg.Wait()    fmt.Println("Terminating Program")}func printPrime(prefix string) {    defer wg.Done()next:    for outer := 2; outer < 5000; outer++ {        for inner := 2; inner < outer; inner++ {            if outer%inner == 0 {                continue next            }        }        fmt.Printf("%s:%d\n", prefix, outer)    }    fmt.Println("Completed", prefix)}

printPrime 这个函数作用是查找显示 5000 以内的素数值,这是一个比较耗时的程序。

运行结果:数字比较多,不打印了,但是可以看到结果是A和B两个协程之间的切换。

上面的代码都是设置GOMAXPROCS为1的情况,给每个可用的核心分配一个逻辑处理器

runtime.GOMAXPROCS(runtime.NumCPU())

这样运行第一个程序的结果如下

Start Goroutines
等待执行结束133333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333332111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222

可以看到1、2、3没有什么规律。

二、Goroutine的竞争

什么竞争状态:如果两个或者多个goroutine 在没有互相同步的情况下,访问某个共享的资源,并试图同时读和写这个资源,就处于相互竞争的状态,这种情况被称作竞争状态(race candition)

竞争状态是比较容易出现问题的地方,所以对一个共享资源的读和写操作必须是原子化的,换句话说,同一时刻只能有一个goroutine 对共享资源进行读和写操作。

package mainimport (    "fmt"    "runtime"    "sync")var (    counter int    wg sync.WaitGroup)func main() {    wg.Add(2)    go incCounter(1)    go incCounter(2)    wg.Wait()    fmt.Println("Final Counter:", counter)}func incCounter(id int) {    defer wg.Done()    for count := 0; count < 2; count++ {        value := counter        runtime.Gosched()        value++        counter = value    }}

运行结果:

Final Counter: 2

变量 counter 会进行4 次读和写操作,每个goroutine 执行两次。但是,程序终止时,counter变量的值为2,这是因为两个协程之间产生了竞争,对同一个counter进行读写,下面这个图很好的诠释了为什么最后counter是2

go语言中的协程实例分析

补充:runtime.Gosched()用于让出CPU时间片。

三、使用Goroutine的

一种修正代码、消除竞争状态的办法是,使用Go 语言提供的锁机制,来锁住共享资源,

从而保证goroutine 的同步状态。Go 语言提供了传统的同步goroutine 的机制,就是对共享资源加锁。

1、原子函数

使用atomic包来提供对数值类型的安全访问。

package mainimport (    "fmt"    "runtime"    "sync"    "sync/atomic")var (    counter int64    wg sync.WaitGroup)func main() {    wg.Add(2)    go incCounter(1)    go incCounter(2)    wg.Wait()    fmt.Println("Final Counter:", counter)}func incCounter(id int) {    defer wg.Done()    for count := 0; count < 2; count++ {        atomic.AddInt64(&counter, 1)        runtime.Gosched()    }}

运行结果

Final Counter: 4

atmoic 包的AddInt64 函数。这个函数会同步整型值的加法,方法是强制同一时刻只能有一个goroutine 运行并完成这个加法操作。当goroutine 试图去调用任何原子函数时,这些goroutine 都会自动根据所引用的变量做同步处理。

另外两个有用的原子函数是LoadInt64 和StoreInt64。这两个函数提供了一种安全地读和写一个整型值的方式。

package mainimport (    "fmt"    "sync"    "sync/atomic"    "time")var (    shutdown int64    wg sync.WaitGroup)func main() {    wg.Add(2)    go doWork("A")    go doWork("B")    time.Sleep(1 * time.Second)    fmt.Println("Shutdown Now")    atomic.StoreInt64(&shutdown, 1)    wg.Wait()}func doWork(name string) {    defer wg.Done()    for {        fmt.Printf("Doing %s Work\n", name)        time.Sleep(250 * time.Millisecond)        // Do we need to shutdown.        if atomic.LoadInt64(&shutdown) == 1 {            fmt.Printf("Shutting %s Down\n", name)            break        }    }}

主协程main函数执行了1秒,然后将shutdown中的值设置为1,A和B的协程中通过读取shutdown是否等于1来判断是否结束协程。

运行结果:

Doing A Work
Doing B Work
Doing A Work
Doing B Work
Doing A Work
Doing B Work
Doing A Work
Doing B Work
Shutdown Now
Shutting A Down
Shutting B Down

互斥锁

另一种同步访问共享资源的方式是使用互斥锁(mutex)。互斥锁这个名字来自互斥(mutualexclusion)的概念。互斥锁用于在代码上创建一个临界区,保证同一时间只有一个goroutine 可以执行这个临界区代码

package mainimport (    "fmt"    "runtime"    "sync")var (    counter int    wg sync.WaitGroup    mutex sync.Mutex)func main() {    wg.Add(3)    go incCounter(1)    go incCounter(2)    go incCounter(3)    wg.Wait()    fmt.Printf("Final Counter: %d\n", counter)}func incCounter(id int) {    defer wg.Done()    for count := 0; count < 2; count++ {        mutex.Lock()        {            value := counter            runtime.Gosched()            value++            counter = value        }        mutex.Unlock()    }}

运行结果:

Final Counter: 6

如上面的代码,最终的结果还是6.

mutex.lock he mutex.Unlock之间的代码,同一时刻只允许一个goroutine进入,所以保证数据的正确性。

补充对于 runtime.Gosched()的理解,有两个问题 1. 当执行了这句话,貌似让出了执行,那么它后面的语句还能执行么

感谢各位的阅读,以上就是“go语言中的协程实例分析”的内容了,经过本文的学习后,相信大家对go语言中的协程实例分析这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

--结束END--

本文标题: go语言中的协程实例分析

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

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

猜你喜欢
  • go语言中的协程实例分析
    这篇文章主要讲解了“go语言中的协程实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“go语言中的协程实例分析”吧!协程的特点该任务的业务代码主动要求切换,即主动让出执行权限发生了IO,...
    99+
    2023-07-02
  • Go语言中协程和线程的对比分析
    Go语言协程(Goroutine)与线程(Thread)是并发编程中常见的两种概念,它们都可以用来处理并发任务,但在实现方式、调度方式、资源消耗等方面有着显著的不同。本文将深入探讨Go...
    99+
    2024-02-25
    go语言 线程 协程
  • Go语言中的闭包实例分析
    这篇文章主要介绍“Go语言中的闭包实例分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Go语言中的闭包实例分析”文章能帮助大家解决问题。一、函数的变量作用域和可见性全局变量在main函数执行之前初...
    99+
    2023-07-02
  • Go 语言结构实例分析
    当前的调试部分可以使用 go run filename.go 来执行。 可以生成一个 build.sh 脚本,用于在指定位置产生已编译好的 可执文件: #!/usr/bin/en...
    99+
    2024-04-02
  • R语言关于协方差分析实例分析
    我们使用回归分析创建模型,描述变量在预测变量对响应变量的影响。 有时,如果我们有一个类别变量,如Yes / No或Male / Female等。简单的回归分析为分类变量的每个值提供多...
    99+
    2024-04-02
  • Go语言中go程释放操作的示例分析
    小编给大家分享一下Go语言中go程释放操作的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!go适合做什么go是golang的简称,而golang可以做服务...
    99+
    2023-06-14
  • PHP协程中Go+Chan+Defer的示例分析
    这篇文章主要为大家展示了“PHP协程中Go+Chan+Defer的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“PHP协程中Go+Chan+Defer的示例分析”这篇文章吧。Swoole...
    99+
    2023-06-16
  • Go语言选择器实例分析
    今天小编给大家分享一下Go语言选择器实例分析的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。引言在 Go 语言中,表达式&nb...
    99+
    2023-07-02
  • Go语言中interface语法与使用实例分析
    这篇“Go语言中interface语法与使用实例分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Go语言中interfac...
    99+
    2023-07-02
  • go语言中的协程详解
    协程的特点 1.该任务的业务代码主动要求切换,即主动让出执行权限 2.发生了IO,导致执行阻塞(使用channel让协程阻塞) 与线程本质的不同 C#、java中我们执行多个线程,是...
    99+
    2024-04-02
  • Go语言中的for循环使用实例分析
    这篇文章主要介绍“Go语言中的for循环使用实例分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Go语言中的for循环使用实例分析”文章能帮助大家解决问题。问题案例一:取地址符在 Go 语言中,我...
    99+
    2023-07-04
  • Go语言流程控制的示例分析
    这篇文章给大家分享的是有关Go语言流程控制的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。1、流程控制流程控制在编程语言中是最伟大的发明了,因为有了它,你可以通过很简单的流程描述来表达很复杂的逻辑。流程控...
    99+
    2023-06-29
  • C语言中实现协程案例
    协程是一种用户空间的非抢占式线程,主要用来解决等待大量的IO操作的问题。 协程vs线程 对比使用多线程来解决IO阻塞任务,使用协程的好处是不用加锁,访问共享的数据不用进行同步操作。这...
    99+
    2024-04-02
  • Go语言中通道channel的示例分析
    这篇文章将为大家详细讲解有关Go语言中通道channel的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。一、Go语言通道基础概念1.channel产生背景    线程...
    99+
    2023-06-29
  • Go语言结构的示例分析
    这篇文章主要为大家展示了“Go语言结构的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Go语言结构的示例分析”这篇文章吧。当前的调试部分可以使用 go run filename.go 来...
    99+
    2023-06-20
  • Go语言中Get/Post请求测试实例分析
    本篇内容主要讲解“Go语言中Get/Post请求测试实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Go语言中Get/Post请求测试实例分析”吧!gin安装先将gin安装一下,安装依赖g...
    99+
    2023-06-30
  • java协程框架quasar和kotlin中的协程实例分析
    这篇文章主要介绍“java协程框架quasar和kotlin中的协程实例分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“java协程框架quasar和kotlin中的协程实例分析”文章能帮助大家解...
    99+
    2023-06-29
  • Go语言正则表达式实例分析
    这篇文章主要介绍了Go语言正则表达式实例分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Go语言正则表达式实例分析文章都会有所收获,下面我们一起来看看吧。前言在计算中,我们经常需要将特定模式的字符或字符子集匹...
    99+
    2023-06-30
  • Go语言时间包应用实例分析
    这篇文章主要介绍了Go语言时间包应用实例分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Go语言时间包应用实例分析文章都会有所收获,下面我们一起来看看吧。1.前言时间和日期对于任何编程语言来说都是一个非常重要...
    99+
    2023-06-30
  • Go语言中基础闭包的示例分析
    这篇文章将为大家详细讲解有关Go语言中基础闭包的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。一. 闭包概述闭包就是解决局部变量不能被外部访问的一种解决方案闭包是把函数当作返回值的一种应用二. 代...
    99+
    2023-06-25
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作