我正在尝试识别或理解一种适当的技术,成语,不管我遇到的是哪种特定的并发编程问题。
为简单起见,假设我有一个实时图形用户界面(UI),该界面始终以10Hz的频率重新绘制在屏幕上。
每当一组不同线程中的至少一个实例正在运行时,我想在此UI上显示“忙碌”指示器,并且我希望当这些线程中恰好有0个正在运行时,该指示器停止显示。只要用户界面启动,这些线程就可以随时启动和停止。
我目前正在golang中实现此功能(相关代码段将在下面进行进一步介绍)。但总的来说,我可以通过以下方式解决此问题:
保留对通过互斥量waitCount
保护的计数器int waitLock
(请求我们表示“繁忙”的线程数)的访问权限。
功能drawStatus()
:重绘整个UI(每100毫秒发生一次):
waitLock
waitCount
> 0:
waitLock
函数startWait()
:当线程需要指示忙时:
waitLock
waitCount
waitLock
函数stopWait()
:当线程不再需要指示忙时:
waitLock
waitCount
waitLock
对我来说,我好像没有充分利用golang的并发功能,而没有使用我熟悉的互斥锁。但即使如此,此代码中仍然存在一个错误,其中“忙碌”指示器过早被关闭。
老实说,我不是在寻找任何人来帮助您识别该错误,而是要传达我感兴趣的特定逻辑。有没有更惯用的golang方法来解决此问题?还是我应该研究更通用的编程模式?我使用的这项技术是否有任何特定名称?正确执行此操作的建议或指示将非常有用。谢谢。
这是一些实现上述逻辑的医生片段
var WaitCycle = [...]rune{'', '', '', '', '', '', '', ''}
// type Layout holds the high level components of the terminal user interface
type Layout struct {
//
// ... other fields hidden for example ...
//
waitLock sync.Mutex
waitIndex int // the current index of the "busy" rune cycle
waitCount int // the current number of tasks enforcing the "busy" state
}
// function show() starts drawing the user interface.
func (l *Layout) show() *ReturnCode {
// timer forcing a redraw @ 10Hz
go func(l *Layout) {
tick := time.NewTicker(100 * time.Millisecond)
defer tick.Stop()
for {
select {
case <-tick.C:
// forces the UI to redraw all changed screen regions
l.ui.QueueUpdateDraw(func() {})
}
}
}(l)
if err := l.ui.Run(); err != nil {
return rcTUIError.specf("show(): ui.Run(): %s", err)
}
return nil
}
// function drawStatus() draws the "Busy" indicator at a specific UI position
func (l *Layout) drawStatus(...) {
l.waitLock.Lock()
if l.waitCount > 0 {
l.waitIndex = (l.waitIndex + 1) % WaitCycleLength
waitRune := fmt.Sprintf(" %c ", WaitCycle[l.waitIndex])
drawToScreen(waitRune, x-1, y, width)
}
l.waitLock.Unlock()
}
// function startWait() safely fires off the "Busy" indicator on the status bar
// by resetting the current index of the status rune cycle and incrementing the
// number of goroutines requesting the "Busy" indicator.
func (l *Layout) startWait() {
l.waitLock.Lock()
if 0 == l.waitCount {
l.waitIndex = 0
}
l.waitCount++
l.waitLock.Unlock()
}
// function stopWait() safely hides the "Busy" indicator on the status bar by
// decrementing the number of goroutines requesting the "Busy" indicator.
func (l *Layout) stopWait() {
l.waitLock.Lock()
l.waitCount--
l.waitLock.Unlock()
}
答案 0 :(得分:3)
由于您要做的只是锁定单个计数器,因此可以简化并仅使用sync/atomic包。启动goroutine时调用AddInt32(&x, 1)
,结束时调用AddInt32(&x, -1)
。从您的绘图程序中调用LoadInt32(&x)
。
答案 1 :(得分:1)
这取决于用例(您可以选择所需的内容,直到出现错误或造成性能损失之前,您的身体都不会在意),通道将Lock隐藏在其中,并使编码更简单会降低一些性能成本-因此,除非您考虑提高性能,否则我建议在一般使用情况下使用渠道):
在以下情况下使用频道:
1-转让所有权
2-协调
在以下情况下使用基元:
3-对性能至关重要
4-保护结构的内部状态
参考:page 33
由于您正在使用软实时UI来协调goroutine的数量,并且对性能没有要求,因此,我建议使用通道,因此简化了代码,在此示例中 :
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
for i := 0; i < 100; i++ {
go job() // e.g.: run all jobs
}
busy := 0
time.Sleep(10 * time.Millisecond) // or make sure at least on goroutine started
// 10Hz:
tick := time.NewTicker(100 * time.Millisecond)
defer tick.Stop()
for {
select {
case n := <-ch:
busy += n
case <-tick.C:
// forces the UI to redraw all changed screen regions
fmt.Printf(" %d \r", busy)
if busy == 0 {
return
}
}
}
}
func job() {
ch <- +1
time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond)
ch <- -1
}
var ch = make(chan int, 1)