routine是什么意思

更新时间:2022-11-25 05:36:03 阅读: 评论:0


2022年11月25日发(作者:大附件邮箱)

go获取协程(goroutine)号的实例

我就废话不多说了,⼤家还是直接看代码吧~

funcGetGID()uint64{

b:=make([]byte,64)

b=b[:(b,fal)]

b=efix(b,[]byte("goroutine"))

b=b[:yte(b,'')]

n,_:=int(string(b),10,64)

returnn

}

补充:Go语⾔并发协程Goroutine和通道channel

Go语⾔并发协程Goroutine

1.1Go语⾔竞争状态

有并发,就有资源竞争,如果两个或者多个goroutine在没有相互同步的情况下,访问某个共享的资源,⽐如同时对该资源进

⾏读写时,就会处于相互竞争的状态,这就是并发中的资源竞争。

并发本⾝并不复杂,但是因为有了资源竞争的问题,就使得我们开发出好的并发程序变得复杂起来,因为会引起很多莫名其妙

的问题。

以下代码就会出现竞争状态:

import(

"fmt"

"runtime"

"sync"

)

var(

countint32

oup

)

funcmain(){

(2)

goincCount()

goincCount()

()

n(count)

}

funcincCount(){

()

fori:=0;i<2;i++{

value:=count

d()

value++

count=value

}

}

count变量没有任何同步保护,所以两个goroutine都会对其进⾏读写,会导致对已经计算好的结果被覆盖,以⾄于产⽣错误

结果。

代码中的d()是让当前goroutine暂停的意思,退回执⾏队列runq,让其他等待的goroutine运⾏,⽬的是为

了使资源竞争的结果更明显,下次运⾏暂停的goroutine时从断点处开始。

分析程序运⾏过程:

g1读取到count的值为0;

然后g1暂停了,切换到g2运⾏,g2读取到count的值也为0;

g2暂停,切换到g1暂停的位置继续运⾏,g1对count+1,count的值变为1;

g1暂停,切换到g2,g2刚刚已经获取到值0,对其+1,最后赋值给count,其结果还是1;

可以看出g1对count+1的结果被g2给覆盖了,两个goroutine都+1⽽结果还是1。

通过上⾯的分析可以看出,之所以出现上⾯的问题,是因为两个goroutine相互覆盖结果。

所以我们对于同⼀个资源的读写必须是原⼦化的,也就是说,同⼀时间只能允许有⼀个goroutine对共享资源进⾏读写操作。

此例⼦的共享资源就是count

通过gobuild-race⽣成⼀个可以执⾏⽂件,然后再运⾏这个可执⾏⽂件,就可以检测资源竞争信息,看到打印出的检测信

息。如下

==================

WARNING:DATARACE

Readat0x000000619cbcbygoroutine8:

nt()

D:/code/src/:25+0x80//goroutine8在代码25⾏读取共享资源value:=count

Previouswriteat0x000000619cbcbygoroutine7:

nt()

D:/code/src/:28+0x9f//goroutine7在代码28⾏修改共享资源count=value

Goroutine8(running)createdat:

()

D:/code/src/:17+0x7e

Goroutine7(finished)createdat:

()

D:/code/src/:16+0x66//两个goroutine都是从main函数的16、17⾏通过go关键字启动的。

==================

4

Found1datarace(s)

1.2锁住共享资源

Go语⾔提供了传统的同步goroutine的机制,就是对共享资源加锁。atomic和sync包⾥的⼀些函数就可以对共享的资源进⾏

加锁操作。

1.2.1原⼦函数

原⼦函数能够以很底层的加锁机制来同步访问整型变量和指针

import(

"fmt"

"runtime"

"sync"

"sync/atomic"

)

var(

counterint64

oup

)

funcmain(){

(2)

goincCounter(1)

goincCounter(2)

()//等待goroutine结束

n(counter)

}

funcincCounter(idint){

()

forcount:=0;count<2;count++{

64(&counter,1)//安全的对counter加1

d()

}

}

上述代码中使⽤了atmoic包的AddInt64函数,这个函数会同步整型值的加法,⽅法是强制同⼀时刻只能有⼀个gorountie运

⾏并完成这个加法操作。

另外两个有⽤的原⼦函数是LoadInt64和StoreInt64。这两个函数提供了⼀种安全地读和写⼀个整型值的⽅式。下⾯的代码就

使⽤了LoadInt64和StoreInt64函数来创建⼀个同步标志,这个标志可以向程序⾥多个goroutine通知某个特殊状态。

import(

"fmt"

"sync"

"sync/atomic"

"time"

)

var(

shutdownint64

oup

)

funcmain(){

(2)

godoWork("A")

godoWork("B")

(1*)

n("ShutdownNow")

nt64(&shutdown,1)

()

}

funcdoWork(namestring){

()

for{

("Doing%sWorkn",name)

(250*econd)

t64(&shutdown)==1{

("Shutting%sDownn",name)

break

}

}

}

--output--

DoingAWork

DoingBWork

DoingBWork

DoingAWork

DoingAWork

DoingBWork

DoingBWork

DoingAWork//前8⾏顺序每次运⾏时都不⼀样

ShutdownNow

ShuttingADown

ShuttingBDown//A和B都shutdown后,由()把计数器置0

上⾯代码中main函数使⽤StoreInt64函数来安全地修改shutdown变量的值。如果哪个doWorkgoroutine试图在main函数

调⽤StoreInt64的同时调⽤LoadInt64函数,那么原⼦函数会将这些调⽤互相同步,保证这些操作都是安全的,不会进⼊竞争

状态。

1.2.2锁

见上篇⽂章,上⾯的例⼦为保持同步,取消竞争,可照以下操作:

funcincCounter(idint){

()

forcount:=0;count<2;count++{

//同⼀时刻只允许⼀个goroutine进⼊这个临界区

()

{

value:=counter

d()//退出当前goroutine,调度器会再次分配这个goroutine继续运⾏。

value++

counter=value

}

()//释放锁,允许其他正在等待的goroutine进⼊临界区

}

}

1.3通道chan

统统将通道两端的goroutine理解为⽣产者-消费者模式。

通道的数据接收⼀共有以下4种写法。

阻塞接收数据

阻塞模式接收数据时,将接收变量作为<-操作符的左值,格式如下:

data:=<-ch

执⾏该语句时将会阻塞,直到接收到数据并赋值给data变量。

2)⾮阻塞接收数据

使⽤⾮阻塞⽅式从通道接收数据时,语句不会发⽣阻塞,格式如下:

data,ok:=<-ch

data:表⽰接收到的数据。未接收到数据时,data为通道类型的零值。

ok:表⽰是否接收到数据。

⾮阻塞的通道接收⽅法可能造成⾼的CPU占⽤,因此使⽤⾮常少。如果需要实现接收超时检测,可以配合lect和计时器

channel进⾏

3)循环接收数据

import(

"fmt"

"time"

)

funcmain(){

//构建⼀个通道,这⾥有没有缓冲都可,因为是收了就发,⽆需阻塞等待

ch:=make(chanint)

//开启⼀个并发匿名函数

gofunc(){

//从3循环到0

fori:=3;i>=0;i--{

//发送3到0之间的数值

ch<-i

//每次发送完时等待

()

}

}()

//遍历接收通道数据

fordata:=rangech{

//打印通道数据

n(data)

//当遇到数据0时,退出接收循环

ifdata==0{

break

}

}

}

--output--

1.3.1单向通道

ch:=make(chanint)

//声明⼀个只能写⼊数据的通道类型,并赋值为ch

varchSendOnlychan<-int=ch

ch:=make(chan<-int)

//声明⼀个只能读取数据的通道类型,并赋值为ch

varchRecvOnly<-chanint=ch

ch:=make(<-chanint)

1.3.2优雅的关闭通道

1.3.3⽆缓冲的通道

如果两个goroutine没有同时准备好,通道会导致先执⾏发送或接收操作的goroutine阻塞等待。(阻塞指的是由于某种原因

数据没有到达,当前协程(线程)持续处于等待状态,直到条件满⾜才解除阻塞)这种对通道进⾏发送和接收的交互⾏为本⾝

就是同步的。其中任意⼀个操作都⽆法离开另⼀个操作单独存在。

在⽹球⽐赛中,两位选⼿会把球在两个⼈之间来回传递。选⼿总是处在以下两种状态之⼀,要么在等待接球,要么将球打向对

⽅。可以使⽤两个goroutine来模拟⽹球⽐赛,并使⽤⽆缓冲的通道来模拟球的来回

//这个⽰例程序展⽰如何⽤⽆缓冲的通道来模拟

//2个goroutine间的⽹球⽐赛

packagemain

import(

"fmt"

"math/rand"

"sync"

"time"

)

//wg⽤来等待程序结束

oup

funcinit(){

(().UnixNano())

}

//main是所有Go程序的⼊⼝

funcmain(){

//创建⼀个⽆缓冲的通道

court:=make(chanint)

//计数加2,表⽰要等待两个goroutine

(2)

//启动两个选⼿

goplayer("Nadal",court)

goplayer("Djokovic",court)

//发球

court<-1

//等待游戏结束

()

}

//player模拟⼀个选⼿在打⽹球

funcplayer(namestring,courtchanint){

//在函数退出时调⽤Done来通知main函数⼯作已经完成

()

for{

//等待球被击打过来

ball,ok:=<-court

if!ok{

//如果通道被关闭,我们就赢了

("Player%sWonn",name)

return

}

//选随机数,然后⽤这个数来判断我们是否丢球

n:=(100)

ifn%13==0{

("Player%sMisdn",name)

//关闭通道,表⽰我们输了

clo(court)

return

}

//显⽰击球数,并将击球数加1

("Player%sHit%dn",name,ball)

ball++

//将球打向对⼿,为啥这⾥是把ball发送到另⼀个go协程?

//因为court⽆缓冲,此时另⼀个go协程正好在等待接收court内的值,所以此时转向另⼀个go协程代码

court<-ball

}

}

1.3.4有缓冲的通道

有缓冲的通道是⼀种在被接收前能存储⼀个或者多个值的通道。这种类型的通道并不强制要求goroutine之间必须同时完成发

送和接收,发送和接受的阻塞条件为只有在通道中没有要接收的值时,接收动作才会阻塞。只有在通道没有可⽤缓冲区容纳被

发送的值时,发送动作才会阻塞。

有缓冲的通道和⽆缓冲的通道之间的⼀个很⼤的不同:⽆缓冲的通道保证进⾏发送和接收的goroutine会在同⼀时间进⾏数据

交换;有缓冲的通道没有这种保证。

为什么要给通道限制缓冲区⼤⼩?

通道(channel)是在两个goroutine间通信的桥梁。使⽤goroutine的代码必然有⼀⽅提供数据,⼀⽅消费数据。当提供数据

⼀⽅的数据供给速度⼤于消费⽅的数据处理速度时,如果通道不限制长度,那么内存将不断膨胀直到应⽤崩溃。因此,限制通

道的长度有利于约束数据提供⽅的供给速度,供给数据量必须在消费⽅处理量+通道长度的范围内,才能正常地处理数据。

1.3.5channel超时机制

lect机制不是专门为超时⽽设计的,却能很⽅便的解决超时问题,因为lect的特点是只要其中有⼀个ca已经完成,程

序就会继续往下执⾏,⽽不会考虑其他ca的情况。

基本语句为:

每个ca语句⾥必须是⼀个IO操作,

lect{

ca<-chan1:

//如果chan1成功读到数据,则进⾏该ca处理语句

cachan2<-1:

//如果成功向chan2写⼊数据,则进⾏该ca处理语句

default:

//如果上⾯都没有成功,则进⼊default处理流程

}

例⼦,注意之所以输出5个num,是因为lect⾥的在这⾥的意思是ch通道⽆值可以接收的时候的3s后才print超时,

即最多ch通道最多阻塞等待3s

funcmain(){

ch:=make(chanint)

quit:=make(chanbool)

//新开⼀个协程

gofunc(){

for{

lect{

canum:=<-ch:

n("num=",num)

ca<-(3*):

n("超时")

quit<-true

}

}

}()//别忘了()

fori:=0;i<5;i++{

ch<-i

()//主协程进⼊休眠状态,等待上⾯的go协程运⾏并进⼊阻塞等待状态,就这样来回运⾏,并通过chan通信

}

<-quit

n("程序结束")

}

--output--

num=0

num=1

num=2

num=3

num=4

超时

程序结束

以上为个⼈经验,希望能给⼤家⼀个参考,也希望⼤家多多⽀持。如有错误或未考虑完全的地⽅,望不吝赐教。

本文发布于:2022-11-25 05:36:03,感谢您对本站的认可!

本文链接:http://www.wtabcd.cn/fanwen/fan/90/16572.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图