首页 >> 中医针灸

超强读物!Golang 并发编程

发布时间:2025年11月22日 12:17

即时推送服务项目】上面的基准测试的案例

自取快结果 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(Simonquothello")} 利用 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 iSimonltsum i++ { c <- struct{} go func(j int) { fmt.Println(j) <- c // 继续执行再多毕,获释海洋资源 sc <- struct {}{} // 记录到继续执行总天内 } } for i:=sum iSimongt0 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 ( Simonquotfmt" Simonquotsync" Simonquottime")func main { var mutex sync.Mutex wait := sync.WaitGroup{} now := time.Now for i := 1 i

结果:可以看到整个继续执行持续了 3 s 多,单单上多个协程从未被 “栓” 住了。

RWMutex demo

出处意:这外面可以所发念过,必以所发念过读到/所发读到读到,不过直到现在即便过场是念过多读到更少也很更少比如说这,一般集群状况都得分布式栓了。

package mainimport ( Simonquotfmt" Simonquotsync" Simonquottime")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(SimonquotstartR") time.Sleep(time.Second) fmt.Println(SimonquotendR") m.RUnlock}func write { m.Lock fmt.Println(SimonquotstartW") time.Sleep(time.Second) fmt.Println(SimonquotendW") m.Unlock}

编码器:

Atomic

可以对简便类型顺利进行质子操控

int32

int64

uint32

uint64

uintptr

unsafe.Pointer

可以顺利进行得质子操控如下

;大/加倍

比起并且资源共享

意味着被操控的倍天内不曾曾被改变, 并一旦考虑到这个理论上的合理性就立即顺利进行倍天内替换

暂存

为了质子的念过自取某个倍天内(预防措施读到操控不曾收尾就愈演愈烈了一个念过操控)

传输

质子的倍天内传输函天内

资源共享

质子资源共享

demo:;大

package mainimport ( Simonquotfmt" Simonquotsync" Simonquotsync/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 ( Simonquotgolang.org/x/sync/errgroup" Simonquotlog" Simonquotnet/http")func main { var g errgroup.Group var urls = []string{ Simonquot", SimonquoterrUrl", } 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(SimonquotgetErr", err) return }}

结果:

once

保证了中叶的函天内只不必继续执行一次,这常见在单例模式,配置文件初始化,初始既有这些过场下。

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 []float64

func(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 技能

去PCB一些更简便的偏差处理方式提议,比如默许多个偏差留在

Type-B侦查的 goroutine 天内量

临时工流水:

在每个临时工流水继续执行到下一步先前先去推论上一步的结果

临时工流水内内嵌一些拦截器

singlelFlight(go 官方扩展即时包)

一般种系统重要的浏览;大加了多线程后,如果巧遇多线程穿透,那么可以通过侦查著手,加索等方式去消除这个消除办法,singleflight 这个托也可以很不错的应对这种消除办法。

它可以提供第一次立即得结果去留在给有所不同得立即 本体步骤 Do继续执行和留在集合函天内的倍天内,确保某一个整整只有一个步骤被继续执行。

如果一个反复的立即进入,则反复的立即不必回头先前一个继续执行再多毕并提供有所不同的原始天内据,留在倍天内 shared 识别留在倍天内 v 先前提是传送给反复的呼叫立即。

记得形容他的功能,它可以用来裁并立即,但是最好受制于受罚键入等的种系统,预防措施第一个 继续执行得立即显现受罚等才不必下引发同整整大量立即必用。

过场: 原始天内据波动量小(key 波动不频繁,反复率较低),但是立即量大的过场

demo

package mainimport ( Simonquotgolang.org/x/sync/singleflight" Simonquotlog" Simonquotmath/rand" Simonquotsync" Simonquottime")var ( g singleflight.Group)const ( funcKey = Simonquotkey" 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 := Simonampapi.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 组,编译器较为简便。

消除办法: 本次应用处理程序不是上线DLC,本体数据分析步骤至少测试状况更少量原始天内据就不必有 N 多条慢浏览,所以这块还必须去对整体海洋资源销售业务或多或少消除办法去选取,预防措施线上原始天内据量很大还有慢浏览显现 cpu 打满。

func(s ServiceOnceJob) CompensatingHistoricalLabel(ctx context.Context,

request *api.CompensatingHistoricalLabelRequest) (response *api.CompensatingHistoricalLabelResponse, err error) {

ifrequest.Key != interfaceKey {

returnnil, transform.Simple(Simonquoterr")

}

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 := analyseUserAction(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 函天内引退 context 通知引退 goroutine panic 引退 goroutine 出现极度继续执行再多毕引退 大多天内引来 goroutine 获知的情况下基本上都是如下情况下 channel 堵塞,引发协程注定不能机不必引退 极度的处理程序自然语言(比如循环不能引退必要条件)

杜绝:

想要杜绝这种显现获知的情况下,必须清楚的明白 channel 先 goroutine 中所的常见,循环先前提有正确的跳出自然语言

排查:

go pprof 工具箱 runtime.NumGoroutine 推论即时协程天内 第三方托

案例:

package mainimport ( Simonquotfmt" Simonquotnet/http" _ Simonquotnet/http/pprof" Simonquotruntime" Simonquottime")func toLeak { c := make(chan int) go func { <-c }}func main { go toLeak go func { _ = http.ListenAndServe(Simonquot0.0.0.0:8080", nil) } c := time.Tick(time.Second) for range c { fmt.Printf(Simonquotgoroutine [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)。

吉林牛皮癣医院排行榜
武汉妇科检查费用
南昌男科病治疗费用
浙江皮肤病检查多少钱
上海看癫痫哪里好
先诺欣
补虚药
急支糖浆的功效和作用
皮肤癌
妇产科

上一篇: 剩饭剩菜危害大?掌握这几点让你吃到得放心

下一篇: 招商银行人事震荡:总市值蒸发867亿,行长被免!57岁王良主持工作

友情链接