如何限制对单个实时资源的并发访问

时间:2018-12-11 05:51:43

标签: multithreading go concurrency real-time

我正在尝试识别或理解一种适当的技术,成语,不管我遇到的是哪种特定的并发编程问题。

为简单起见,假设我有一个实时图形用户界面(UI),该界面始终以10Hz的频率重新绘制在屏幕上。

每当一组不同线程中的至少一个实例正在运行时,我想在此UI上显示“忙碌”指示器,并且我希望当这些线程中恰好有0个正在运行时,该指示器停止显示。只要用户界面启动,这些线程就可以随时启动和停止。

我目前正在golang中实现此功能(相关代码段将在下面进行进一步介绍)。但总的来说,我可以通过以下方式解决此问题:

  • 保留对通过互斥量waitCount保护的计数器int waitLock(请求我们表示“繁忙”的线程数)的访问权限。

  • 功能drawStatus():重绘整个UI(每100毫秒发生一次):

    1. 获取互斥锁waitLock
    2. 如果int waitCount> 0:
      1. 绘制“忙碌”指示器
    3. 释放互斥锁waitLock
  • 函数startWait():当线程需要指示忙时:

    1. 获取互斥锁waitLock
    2. 增量int waitCount
    3. 释放互斥锁waitLock
  • 函数stopWait():当线程不再需要指示忙时:

    1. 获取互斥锁waitLock
    2. 减量整数waitCount
    3. 释放互斥锁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()
    }

2 个答案:

答案 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)