为高度并发的应用程序实现全局计数器的最佳方法是什么?就我而言,我可能有10K-20K
go例程执行“工作”,并且我想计算这些例程共同处理的项目的数量和类型…
“经典”同步编码样式如下所示:
var work_counter int
func GoWorkerRoutine() {
for {
// do work
atomic.AddInt32(&work_counter,1)
}
}
现在,这变得更加复杂了,因为我想跟踪工作的“类型”,所以实际上我需要这样的东西:
var work_counter map[string]int
var work_mux sync.Mutex
func GoWorkerRoutine() {
for {
// do work
work_mux.Lock()
work_counter["type1"]++
work_mux.Unlock()
}
}
似乎应该使用渠道或类似方式来“优化”优化方式:
var work_counter int
var work_chan chan int // make() called somewhere else (buffered)
// started somewher else
func GoCounterRoutine() {
for {
select {
case c := <- work_chan:
work_counter += c
break
}
}
}
func GoWorkerRoutine() {
for {
// do work
work_chan <- 1
}
}
最后一个示例仍然缺少地图,但是添加起来很容易。这种样式会提供比简单的原子增量更好的性能吗?当我们谈论并发访问全局值而不是可能阻止I /
O完成的事情时,我不能说这是多少复杂?
思想受到赞赏。
2013年5月28日更新:
我测试了几个实现,结果不是我期望的,这是我的反源码:
package helpers
import (
)
type CounterIncrementStruct struct {
bucket string
value int
}
type CounterQueryStruct struct {
bucket string
channel chan int
}
var counter map[string]int
var counterIncrementChan chan CounterIncrementStruct
var counterQueryChan chan CounterQueryStruct
var counterListChan chan chan map[string]int
func CounterInitialize() {
counter = make(map[string]int)
counterIncrementChan = make(chan CounterIncrementStruct,0)
counterQueryChan = make(chan CounterQueryStruct,100)
counterListChan = make(chan chan map[string]int,100)
go goCounterWriter()
}
func goCounterWriter() {
for {
select {
case ci := <- counterIncrementChan:
if len(ci.bucket)==0 { return }
counter[ci.bucket]+=ci.value
break
case cq := <- counterQueryChan:
val,found:=counter[cq.bucket]
if found {
cq.channel <- val
} else {
cq.channel <- -1
}
break
case cl := <- counterListChan:
nm := make(map[string]int)
for k, v := range counter {
nm[k] = v
}
cl <- nm
break
}
}
}
func CounterIncrement(bucket string, counter int) {
if len(bucket)==0 || counter==0 { return }
counterIncrementChan <- CounterIncrementStruct{bucket,counter}
}
func CounterQuery(bucket string) int {
if len(bucket)==0 { return -1 }
reply := make(chan int)
counterQueryChan <- CounterQueryStruct{bucket,reply}
return <- reply
}
func CounterList() map[string]int {
reply := make(chan map[string]int)
counterListChan <- reply
return <- reply
}
它使用通道进行写入和读取,这似乎是合乎逻辑的。
这是我的测试用例:
func bcRoutine(b *testing.B,e chan bool) {
for i := 0; i < b.N; i++ {
CounterIncrement("abc123",5)
CounterIncrement("def456",5)
CounterIncrement("ghi789",5)
CounterIncrement("abc123",5)
CounterIncrement("def456",5)
CounterIncrement("ghi789",5)
}
e<-true
}
func BenchmarkChannels(b *testing.B) {
b.StopTimer()
CounterInitialize()
e:=make(chan bool)
b.StartTimer()
go bcRoutine(b,e)
go bcRoutine(b,e)
go bcRoutine(b,e)
go bcRoutine(b,e)
go bcRoutine(b,e)
<-e
<-e
<-e
<-e
<-e
}
var mux sync.Mutex
var m map[string]int
func bmIncrement(bucket string, value int) {
mux.Lock()
m[bucket]+=value
mux.Unlock()
}
func bmRoutine(b *testing.B,e chan bool) {
for i := 0; i < b.N; i++ {
bmIncrement("abc123",5)
bmIncrement("def456",5)
bmIncrement("ghi789",5)
bmIncrement("abc123",5)
bmIncrement("def456",5)
bmIncrement("ghi789",5)
}
e<-true
}
func BenchmarkMutex(b *testing.B) {
b.StopTimer()
m=make(map[string]int)
e:=make(chan bool)
b.StartTimer()
for i := 0; i < b.N; i++ {
bmIncrement("abc123",5)
bmIncrement("def456",5)
bmIncrement("ghi789",5)
bmIncrement("abc123",5)
bmIncrement("def456",5)
bmIncrement("ghi789",5)
}
go bmRoutine(b,e)
go bmRoutine(b,e)
go bmRoutine(b,e)
go bmRoutine(b,e)
go bmRoutine(b,e)
<-e
<-e
<-e
<-e
<-e
}
我实现了一个简单的基准测试,在地图周围只有一个互斥锁(只是测试写操作),并使用5个并行运行的goroutine进行了基准测试。结果如下:
$ go test --bench=. helpers
PASS
BenchmarkChannels 100000 15560 ns/op
BenchmarkMutex 1000000 2669 ns/op
ok helpers 4.452s
我不会期望互斥体会这么快…
进一步的想法?
不要使用同步/原子 -从链接页面
原子包提供了用于实现同步算法的低级原子内存原语。这些功能需要格外小心才能正确使用。除特殊的低层应用程序外,最好通过通道或sync软件包的功能来完成同步
上次我必须执行此操作时,我使用互斥对象对类似于您的第二个示例的东西进行了基准测试,对类似于通道的第三个示例进行了基准测试。当事情真的很忙时,通道代码会赢,但是请确保将通道缓冲区设置得很大。
问题内容: 我正在构建一个node.js应用程序,它是一个REST api,对我的mongodb使用express和mongoose。我现在已经设置了CRUD端点,但是我只是想知道两件事。 如何扩展这种路由方式,特别是如何在路由之间共享模块。我希望我的每条路线都进入一个新文件,但是显然只有一个数据库连接,正如您所看到的,我在people.js顶部包含了猫鼬。 我是否必须在people.js中写出3
问题内容: Java是我选择的编程语言之一。尽管将应用程序分发给最终用户,但我总是遇到问题。 为用户提供JAR并不总是像我想要的那样友好,并且使用Java WebStart要求我维护Web服务器。 分发Java应用程序的最佳方法是什么?如果Java应用程序需要在用户计算机上安装工件,该怎么办?有没有好的Java安装/打包系统? 问题答案: 有多种解决方案,取决于你的发行要求。 只是用一个jar。这
问题内容: 我希望在学习快速的过程中开发一个库存/物品库存应用程序。它基本上是具有项目名称,数量和位置的东西。 例如。 25号工作货车灯泡 开关,6,仓库 当用户输入此数据并按下按钮时,什么是存储此数据并在以后检索的最佳方法。我知道我可以将此附加到数组并显示该数组,但是如果应用程序关闭,该怎么办? 我应该在学习数据库存储吗?我可以将数据保存到手机吗? 问题答案: 如果要存储的数据很少且不敏感,则可
问题内容: 我刚刚完成了将应用程序从Windows移植到Linux的工作。 我必须创建该应用程序的安装程序。 该应用程序 不是 开源的=>我应该分发该应用程序的二进制文件(可执行文件,几个.so文件,帮助文件和图像)。 我不喜欢第一种方法(RPM和DEB软件包),因为我不想为不同的Linux发行版保留不同的软件包。 为Linux分发二进制应用程序的 最佳方法 是什么? 问题答案: 经过几次使用商业
问题内容: 以下是我典型的程序的整体结构。 funA funB并在用户单击按钮1、2、3时funC打开另一个Toplevel带有窗口小部件的窗口。 我想知道这是否是编写python tkinter程序的正确方法吗?当然,即使我这样写也可以,但这是最好的方法吗?听起来很愚蠢,但是当我看到其他人编写的代码时,他们的代码并没有弄乱一堆函数,而且大多数情况下都有类。 有没有作为良好实践应遵循的特定结构?开