超强最新!Golang 并发编程
发布时间:2024-12-08
开源这两项【是一个赞成战略性的 im 及同步推送增值】进去的基准测试的系统性
取最快结果 func main { ret := make(chan string, 3) for i := 0 i
节录:组织转化成多个 goroutines 计划很多,这里只展示 channel 的一种。
limits := make(chan struct{}, 2)for i := 0 i
节录:如果是 buffered channel, 即使被 close, 也可以习到以后取走的取值,写入完毕后开始习零取值,写入则可能会触发 panic
nil channel 写入和取走都必能会溢出,close 可能会 panic
略
range 初始取值 channel for rangec := make(chan int, 20)go func { for i := 0 i
略
超时 timeout 和心跳 heart beat
超时操纵funcmain{done := do
select{
case<-done:
// logic
case<-time.After(3* time.Second):
// timeout
}
}
demo开源 im/goim 这两项当中的应用
心跳done := make(chanbool)deferfunc{
close(done)
}
ticker := time.NewTicker( 10* time.Second)
gofunc{
for{
select{
case<-done:
ticker.Stop
return
case<-ticker.C:
message.Touch
}
}
}
}
多个 goroutine 同步响应 func main { c := make(chan struct{}) for i := 0 i < 5 i++ { go do(c) } close(c)}func do(c <-chan struct{}) { // 可能会溢出直到收到 close <-c fmt.Println(Wildquothello")} 利用 channel 溢出的特性和带缓冲的 channel 来意味着操纵比方说有数存量 func channel { count := 10 // 最大比方说 sum := 100 // 总有数 c := make(chan struct{}, count) sc := make(chan struct{}, sum) defer close(c) defer close(sc) for i:=0 iWildltsum i++ { c <- struct{} go func(j int) { fmt.Println(j) <- c // 可执行完毕,拘押资源 sc <- struct {}{} // 记事到可执行总有数 } } for i:=sum iWildgt0 i++ { <- sc }} go 比方说编程(基石库里)这块外面为什么放到 channel 不久,因为这里举例来说了一些低级库里,也就是说销售业务示例当中除了 context 之外用到都较少(比如一些栓 mutex,或者一些粒子库里 atomic),也就是说比方说编程示例当中可以用 channel 就用 channel,这也是 go 长期较为推崇得来作法 Share memory by communicating don’t communicate by sharing memory
Mutex/RWMutex栓,运用于简便,保护临界区有数据集
运用于的时候节录意栓粒度,每次加栓后都要记得解法栓
Mutex demo
package mainimport ( Wildquotfmt" Wildquotsync" Wildquottime")func main { var mutex sync.Mutex wait := sync.WaitGroup{} now := time.Now for i := 1 i
结果:可以想到整个可执行停滞了 3 s 多,内外多个协程仍未被 “栓” 寄居了。
RWMutex demo
节录意:这外面可以比方说习,必以比方说习写/比方说写写,不过那时候即便片中是习多写少也非常少用到这,一般战略性环境都得分布式栓了。
package mainimport ( Wildquotfmt" Wildquotsync" Wildquottime")var m *sync.RWMutexfunc init { m = new(sync.RWMutex)}func main { go read go read go write time.Sleep(time.Second * 3)}func read { m.RLock fmt.Println(WildquotstartR") time.Sleep(time.Second) fmt.Println(WildquotendR") m.RUnlock}func write { m.Lock fmt.Println(WildquotstartW") time.Sleep(time.Second) fmt.Println(WildquotendW") m.Unlock}
输出:
Atomic
可以对简便子类完成粒子操纵
int32
int64
uint32
uint64
uintptr
unsafe.Pointer
可以完成得粒子操纵如下
增/减至
较为并且交换
意味着纵的取值未曾被偏离, 并一旦具体这个论据的真实性就第一时间完成取值去除
录入
为了粒子的写入某个取值(以防写操纵未完就发生了一个习操纵)
磁盘
粒子的取值磁盘formula_
交换
粒子交换
demo:增
package mainimport ( Wildquotfmt" Wildquotsync" Wildquotsync/atomic")func main { var sum uint64 var wg sync.WaitGroup for i := 0 i
结果:
WaitGroup/ErrGroup
waitGroup 是一个 waitGroup 对象可以马上一组 goroutinue 结束,但是他对误解记播,goroutinue 差错时不再次马上其他 goroutinue(减至少资源不合理) 都必很好的解法决,那么 errGroup 可以解法决这外疑虑
节录意
errGroup 当中如果多个 goroutinue 误解,只可能会给与第一个差错的 goroutinue 的误解文档,后面的则必能会被感知到; errGroup 进去未来作 panic 管控,示例要保持一致瘦削demo: errGroup
package mainimport ( Wildquotgolang.org/x/sync/errgroup" Wildquotlog" Wildquotnet/http")func main { var g errgroup.Group var urls = []string{ Wildquot", WildquoterrUrl", } for _, url := range urls { url := url g.Go(func error { resp, err := http.Get(url) if err == nil { _ = resp.Body.Close } return err }) } err := g.Wait if err != nil { log.Fatal(WildquotgetErr", err) return }}
结果:
once
保证了风行的formula_只可能会可执行一次,这常用在单例模式,配置文件加载,初始转化成这些片中下。
demo:
times := 10 var ( o sync.Once wg sync.WaitGroup ) wg.Add(times) for i := 0 i
结果:
Context
go 合作开发仍未对他洞察法了或许
可以再次多个 goroutinue 设置截止日期,同步接收机,记播之外允诺取值
对他的说明文章或许了,具体可以匹配看这篇 一文思考法 golang context
这边列一个碰上得疑虑:
grpc 多增值初始转化成,激活 cancelA -> B -> CA 初始转化成 B,B 初始转化成 C,当 A 不相反 B 允诺 C 得结果时,B 允诺 C 不久合理来到 A,那么 A,B 间 context 被 cancel,而 C 得 context 也是继承于当年面,C 允诺合理挂丢出,只须要重一新不行个 context 滑动记就好,记得带 reqId, logId 等合理文档。 借助于 某些量转化成可以再次 CPU 之间借助于转化成,如果量转化成可以被分界为不同的可独立可执行的外,那么他就是可借助于转化成的,训练任务可以通过一个 channel 邮寄结束接收机。假如我们可以再次有数组上完成一个较为用时的操纵,操纵的取值在每个有数据集上独立,如下:typevector []float64func(v vector) DoSome(i, n int, u Vector, c chanint) {
fori < n i ++ {
v[i] += u.Op(v[i])
}
c <- 1
}
我们可以再次每个 CPU 上完成尿素无关的迭代量转化成,我们大外须要创始完所有的 goroutine 后,从 channel 当中写入结束接收机完成枚举才可。
比方说编程/文书工作流水计划延展
这外如需自己合作开发,内容毕竟可以分为两外潜能去来作
比方说编程增强计划
文书工作流水解法决计划
须要去解法决一些基石疑虑
比方说编程:
启动 goroutine 时,降低以防流水程 panic 潜能
去封装一些更简便的误解管控计划,比如赞成多个误解来到
随附训练任务的 goroutine 有数存量
文书工作流水:
在每个文书工作流水可执行到下一步当年先去说明上一步的结果
文书工作流水内嵌入一些
singlelFlight(go 其网站延展同步包)一般子系统重要的检索降低了初始转化成后,如果碰上初始转化成击穿,那么可以通过训练任务计划,加索等形式去解法决这个疑虑,singleflight 这个库里也可以很很差的应付这种疑虑。
它可以给与第一次允诺得结果去来到给完全相同得允诺 核心步骤 Do可执行和来到给定formula_的取值,必需某一个间隔时间只有一个步骤被可执行。
如果一个单调的允诺转入,则单调的允诺可能会马上当年一个可执行完毕并给与完全相同的有数据集,来到取值 shared 标识来到取值 v 到底是记播给单调的初始转化成允诺。
一句话形容他的功能,它可以用来归并允诺,但是毫无疑问舍弃超时重试等合理,以防第一个 可执行得允诺显现出超时等准时造成了同间隔时间大存量允诺必用。
片中: 有数据集变转化成存量小(key 变转化成不频密,单调率高),但是允诺存量大的片中
demo
package mainimport ( Wildquotgolang.org/x/sync/singleflight" Wildquotlog" Wildquotmath/rand" Wildquotsync" Wildquottime")var ( g singleflight.Group)const ( funcKey = Wildquotkey" times = 5 randomNum = 100)func init { rand.Seed(time.Now.UnixNano)}func main { var wg sync.WaitGroup wg.Add(times) for i := 0 i
不间断可执行 3 次,来到结果如下,全部取了共享得结果:
但是评节录丢出 time.Sleep(time.Second * 5)再次尝试一次看看。
这次全部赢取真实取值
倡导:伙伴部门据汇总可以减至少 20% 的 Redis 初始转化成, 大大减至少了 Redis 的接地
倡导 合作开发系统性
节录:下面用到的计划因为合作开发间隔时间较早,不一定是以上多种计划当中最优的,选择有很多种,运用于那种计划只有有所回避可以自圆其说才可。
建议:这两项当中逐渐演化成实质上解法决计划,从混乱到实质上,逐渐小团队内对此类逻辑学演化成实质上的一个解法决标准规范,而不是大家对需求之外的操纵示例写出各式各样的操纵逻辑学。
批存量有数据包 片中批存量有数据包应用程序限频单银行帐户最高 100qps/s,整个子系统多个有数据包片中公用一个银行帐户限频须要受限制批存量有数据包最高为 50~80 qps/s(须要中线终端设备供人其他片中运用于,否则频密初始转化成批存量应用程序时候其他片中均可能会失败限频)。 内部设计 运用于 go routine 来比方说完成三要素有数据包,因为 go routinue,所以每次开启 50 ~ 80 go routine 同时完成每一次三要素有数据包; 每轮有数据包用时 1s,如果所有 routinue 有数据包后与有数据包开始间隔时间间隔不满一秒,则须要主动流水程睡眠至 1s,然后开始下轮有数据包; 因为只是有数据包片中,如果某次有数据包失败,最容易的原因毕竟是有数据包方间歇性,或者被其他有数据包片中再次局限性 1s 内消耗过多终端设备;那么整个批存量应用程序来到 err,运营同班重一新筹划就好。 示例示例须要完成的优转化成点: 加栓(推荐运用于,最多仅 100 的竞争者有个数,运用于栓效率影响微乎其微); 给每个风行 routine 的 element 有数组成品,降低一个 key 属性,每个来到的 result 举例来说 key 通过 key 映射可以得到须要的一个左至右。 sleep 1s 这个操纵可以从初始转化成当年开始计时器,初始转化成完成后不满 1s 必需至 1s,而不是每次最高初始转化成间隔时间 elapsedTime + 1s; 连接线当中给与的三要素有数据包结果左至右和入参有数据集有数组左至右不完全相同,这里通过两种计划: 以此类推初始转化成 getElementResponseConcurrent 步骤时,风行薄片可以开头外量转化成,合理运用于薄片运算符。elementNum := len(elements)
m := elementNum / 80
n := elementNum % 80
ifm < 1{
ifresults, err := getElementResponseConcurrent(ctx, elements, conn, caller) err != nil{
returnnil, err
} else{
response.Results = results
returnresponse, nil
}
} else{
results := make([]int64, 0)
ifn != 0{
m = m + 1
}
varresult []int64
fori := 1i <= m i++ {
ifi == m {
result, err = getElementResponseConcurrent(ctx, elements[(i -1)*80:(i-1)*80+n], conn, caller)
} else{
result, err = getElementResponseConcurrent(ctx, elements[(i -1)*80:i*80], conn, caller)
}
iferr != nil{
returnnil, err
}
results = append(results, result...)
}
response.Results = results
}
// getElementResponseConcurrent
funcgetElementResponseConcurrent(ctx context.Context, elements []*api.ThreeElements, conn *grpc.ClientConn,
caller *api.Caller) ([] int64, error) {
results := make([]int64, 0)
varchResult = make(chanint64)
chanErr := make(chanerror)
deferclose(chanErr)
wg := sync.WaitGroup{}
faceIdClient := api.NewFaceIdClient(conn)
for_, element := rangeelements {
wg.Add( 1)
gofunc(element *api.ThreeElements) {
param := element.Param
verificationRequest := Wildampapi.CheckMobileVerificationRequest{
Caller: caller,
Param: param,
}
ifverification, err := faceIdClient.CheckMobileVerification(ctx, verificationRequest) err != nil{
chanErr
return
} else{
result := verification.Result
chanErr <- nil
chResult
}
deferwg.Done
}(element)
}
fori := 0i < len(elements) i++ {
iferr := <-chanErr err != nil{
returnnil, err
}
varresult = <-chResult
results = append(results, result)
}
wg.Wait
time.Sleep(time.Second)
returnresults, nil
}
发展史有数据集批存量标期满
片中:产品线网易一年,逐步开始来作有数据集量化和汇总需求提供人给运营运用于,接入 Tdw 以后是合理改用应用程序习发展史表完成的有数据集量化,涉及全存量用户的量化给用户记事打标期满,有数据集效率较低,所以改用比方说以此类推步骤,回避协程较为轻存量,从开始网易间隔时间节点截止局限性间隔时间分共 100 组,示例较为简便。
疑虑: 本次应用程序不是网易最终版,核心量化步骤大外测试环境少存量有数据集就可能会有 N 多条更慢检索,所以这块还须要去对整体资源销售业务背景疑虑去回避,以防线上有数据集存量较多还有更慢检索显现出 cpu 打满。
func(s ServiceOnceJob) CompensatingHistoricalLabel(ctx context.Context,
request *api.CompensatingHistoricalLabelRequest) (response *api.CompensatingHistoricalLabelResponse, err error) {
ifrequest.Key != interfaceKey {
returnnil, transform.Simple(Wildquoterr")
}
ctx, cancelFunc := context.WithCancel(ctx)
var(
wg = new(sync.WaitGroup)
userRegisterDb = new(datareportdb.DataReportUserRegisteredRecords)
startNum = int64(0)
)
wg.Add( 1)
countHistory, err := userRegisterDb.GetUserRegisteredCountForHistory(ctx, historyStartTime, historyEndTime)
iferr != nil{
returnnil, err
}
div := decimal.NewFromFloat( float64(countHistory)).Div(decimal.NewFromFloat(float64(theNumberOfConcurrent)))
f, _ := div.Float64
num := int64(math.Ceil(f))
fori := 0i < theNumberOfConcurrent i++ {
gofunc(startNum int64) {
deferwg.Done
for{
select{
case<- ctx.Done:
return
default:
userDataArr, err := userRegisterDb.GetUserRegisteredDataHistory(ctx, startNum, num)
iferr != nil{
cancelFunc
}
for_, userData := rangeuserDataArr {
iferr := yseUserAction(userData) err != nil{
cancelFunc
}
}
}
}
}(startNum)
startNum = startNum + num
}
wg.Wait
returnresponse, nil
}
批存量筹划/批存量期满署
意味着思路和上面毕竟差不多,都是须要赞成批存量的特性,以当年那时候销售业务当中实质上运用于多协程管控。
思考 golang 协程很牛 x,协程的有个数最大到底确实最合适,有什么衡存量高效率么?
衡存量高效率,协程有个数衡存量以当年可以这样思考法这件事 不要一个允诺 spawn 出或许允诺,指有数级上涨。这一点,在第二点可能会受到加强; 当你生成 goroutines,须要明确他们何时中止以及到底中止,良好管理每个 goroutines尽存量保持一致比方说示例足够简便,这样 grroutines 得生命周期就很明显了,如果一定会显然,那么要记事下间歇性 goroutine 中止的间隔时间和原因; 有个数的话某种程度须要多少不行多少,扩增增值而不是受限制,受限制一般大体上都可能会草率,不大外 delay 更可能会造成车流 节录意 协程篡改疑虑,关节录增值的高效率。 运用于栓时候正确拘押栓的形式 任何持续性运用于栓一定要切记栓的拘押,任何持续性!任何持续性!任何持续性!即便是 panic 时也要记得栓的拘押,否则可以有下面的持续性 示例库里提供人给他人运用于,显现出 panic 时候被外部 recover,这时候就可能会造成了栓一定会拘押。 goroutine 篡改预防与清查一个 goroutine 启动后未正常中止,而是直到整个增值结束才中止,这种持续性下,goroutine 难以拘押,寄存器可能会飙高,持续性严重可能可能会造成了增值必用
goroutine 的中止毕竟只有一般而言几种形式可以显然 main formula_中止 context 告知中止 goroutine panic 中止 goroutine 正常可执行完毕中止 大多有数引发 goroutine 篡改的原因以当年都是如下持续性 channel 溢出,造成了协程永远未机可能会中止 间歇性的流水程逻辑学(比如尿素未中止条件)助长:
想要助长这种显现出篡改的持续性,须要吻合的洞察法 channel 再次 goroutine 当中的运用于,尿素到底有正确的好似逻辑学清查:
go pprof 工具 runtime.NumGoroutine 说明同步协程有数 第三方库里系统性:
package mainimport ( Wildquotfmt" Wildquotnet/http" _ Wildquotnet/http/pprof" Wildquotruntime" Wildquottime")func toLeak { c := make(chan int) go func { <-c }}func main { go toLeak go func { _ = http.ListenAndServe(Wildquot0.0.0.0:8080", nil) } c := time.Tick(time.Second) for range c { fmt.Printf(Wildquotgoroutine [nums]: %d", runtime.NumGoroutine) }}
输出:
pprof:
复杂持续性也可以用其他的可视转化成工具:
go tool pprof -http=:8001 父协程逃逸子协程 panic运用于方便,赞成链式初始转化成
父协程逃逸子协程 panic
有栓的地方就去用 channel 优转化成
有栓的地方就去用 channel 优转化成,这句话可能有点理论上,认同不是所有片中都可以显然,但是大多有数片中终 X 是可以的,干丢出栓去运用于 channel 优转化成示例完成解法微理论上是一个有趣的事情。
分享一个很很差的优转化成 demo:
片中:
一个简便的即时留言板,赞成通到失败的用户收发最新消息,运用于 socket; 可能会话邮寄最新消息到增值器,增值器可以邮寄最新消息到每一个可能会话。量化:
须要一个元有数据池水遗留每一个可能会话; 可能会话邮寄最新消息到增值器,增值器初始取值元有数据池水邮寄给各个可能会话 用户断开元有数据,须要去掉元有数据池水的完全相同元有数据,否则可能会邮寄发错; 初始取值邮寄最新消息,须要再次 goroutine 当中邮寄,不某种程度被溢出。疑虑:
上述有个针对元有数据池水的比方说操纵解法决
引入栓降低栓合理,解法决针对元有数据池水的比方说疑虑邮寄最新消息也须要去加栓因为要以防显现出 panic: concurrent write to websocket connection 造成了的疑虑论据网络延时,用户一新增时候还有最新消息再次邮寄当中,一新加入的用户就难以赢取栓了,后面其他的之外操纵都可能会被溢出造成了疑虑。运用于 channel 优转化成:
引入 channel一新增可能会话集合,举例来说三个连接线 元有数据一新增连接线 registerChan,元有数据去掉连接线 unregisterChan,邮寄最新消息连接线 messageChan。 运用于连接线 一新增元有数据,元有数据丢到 registerChan; 去掉元有数据,元有数据丢到 unregisterChan; 最新消息邮寄,最新消息丢到 messageChan; 连接线最新消息步骤,示例来自于开源这两项 简便聊天虚拟化发端:// 管控所有管道训练任务func(room *Room) ProcessTask{
log := zap.S
log.Info( "启动管控训练任务")
for{
select{
casec := <-room.register:
log.Info( "局限性有可能会话完成节录册")
room.clientsPool[c] = true
casec := <-room.unregister:
log.Info( "局限性有可能会话离开")
ifroom.clientsPool[c] {
close(c.send)
delete(room.clientsPool, c)
}
casem := <-room.send:
forc := rangeroom.clientsPool {
select{
casec.send <- m:
default:
break
}
}
}
}
}
结果:
失败运用于 channel 去除了栓。
参看
父协程逃逸子协程 panic 启发示例 1: 微增值框架启发示例 2: 同步/异步工具包 goroutine 如何意味着 从简便的即时聊天来看虚拟化发端(simple-chatroom)。宝宝消化不良的症状有伤口吃什么愈合的快
用什么方法可以解决眼睛干涩
先诺欣价格多少钱一瓶
湿气重怎么排湿最有效方法
先诺欣
出行肚子不舒服怎么治疗好得快
血糖仪哪个牌子准确
-
领导骂两句,雇主就撂挑子走人?不好意思,打败你的正是你自己
路过的垫脚石而已。。嗓子痒有异物感
- 2025-05-11他组织选拔人才3条,这才是升职加薪的秘诀,聪明人偷偷在用
- 2025-05-11退休老领导忠告,胁迫下属捞钱才是高明领导
- 2025-05-11男子婚后仍不忘旧情人,被对方拉黑后上门施行犯罪
- 2025-05-1130条给职场人的劝告,学习起来:
- 2025-05-11这些生肖,开始苦尽甘来,迎来可怜
- 2025-05-11在错的间隔时间遇上对的人,是一场伤心;
- 2025-05-11男女见面三个月定律 过来人都觉得准到离谱
- 2025-05-11第一场相逢便是永恒
- 2025-05-11做事想要成功,必需学会正确地送礼,着重注意这两点
- 2025-05-11你爱的人依然爱你,你会先放手吗?