.NET多线程变量访问

时间:2009-07-25 16:19:26

标签: vb.net multithreading

我有一个包含4个线程的应用程序。 (GUI,Controller,Producer,Consumer)

GUI不言自明。

控制器在一些初始设置后启动生产者和消费者线程。

制作人创建项目并将其放置在“环形缓冲区”的空闲槽中

消费者从“环形缓冲区”中获取项目并将它们写入磁盘。

生产者以比消费者高得多的速度创造物品。

消费者IO重,IO限制。

目前我正在检查每个环形缓冲区槽中的变量,以确定它是否可以写入。

if Slot.Free then
  Write Slot.Data To Disk
end if

我没有使用lock / synclock,而只是读取/写入插槽的“free”变量的值。即使它是一个易失性的读/写,我也不相信这是正确的。有没有更好的方法来读/写这个变量?变量的类型为“整数”,为0或1。

5 个答案:

答案 0 :(得分:3)

你提到使用一个环形缓冲区,但是一个(正确实现的)环形缓冲区能够确定它是否已经完整而不检查它的所有元素,从而消除了每个插槽中需要一个布尔值。

我不习惯VB.NET,但这应该是一个环形缓冲区的工作(如果是原始的)实现,它在相应的写/读操作时阻塞它的满/空。

Friend Class RingBuffer(Of T)
    Private _slots() As T
    Private _head As Integer
    Private _tail As Integer

    Private _readableSlots As Semaphore
    Private _readLock As Object
    Private _writableSlots As Semaphore
    Private _writeLock As Object

    Public Sub New(ByVal size As Integer)
        ReDim _slots(size - 1)

        _head = 0
        _tail = 0
        _readLock = New Object
        _writeLock = New Object

        _readableSlots = New Semaphore(0, size)
        _writableSlots = New Semaphore(size, size)
    End Sub

    Public Function Dequeue() As T
        Dim item As T
        _readableSlots.WaitOne()
        SyncLock _readLock
            item = _slots(_head)
            _head = (_head + 1) Mod _slots.Length
        End SyncLock
        _writableSlots.Release()
        Return item
    End Function

    Public Sub Enqueue(ByVal item As T)
        _writableSlots.WaitOne()
        SyncLock _writeLock
            _slots(_tail) = item
            _tail = (_tail + 1) Mod _slots.Length
        End SyncLock
        _readableSlots.Release()
    End Sub
End Class

一旦你有了,你的制作人和消费者可能真的很愚蠢:)如果你有多个消费者,那么并不能完全保证物品按顺序处理:

Private _buffer As RingBuffer(Of Integer) = New RingBuffer(Of Integer)(5)

Private Sub Producer()
    Dim i As Integer = 0
    Do While True
        _buffer.Enqueue(i)
        i = i + 1
    Loop
End Sub

Private Sub Consumer()
    Do While True
        Debug.WriteLine(("Consumer A: " & _buffer.Dequeue))
        Thread.Sleep(1000)
    Loop
End Sub

答案 1 :(得分:1)

有几种方法可以安全地完成这项工作。

  • 如果您的体系结构和要求允许,您可以使用自定义事件,这样一个线程就可以发出不同的监听线程信号,通知变量状态已被更改。您必须跟踪谁正在消耗哪些事件,以及这些消费者是否对变量是只读的,还是读/写。
  • 您还可以在变量类型(或使用通用)周围使用简单的自定义包装类,为您执行锁定/解锁代码。在VB.NET中,我发现使用Monitor类来锁定私有实例变量非常方便。
  • 互斥锁和信号量 - .NET有一个Mutex类和一个Semaphore类。这些都有助于控制对线程共享变量的访问。我喜欢Mutexes,因为它们很容易使用,而且您不需要跟踪有多少线程可以访问给定资源。

请注意,尽管某些MSDN文档声称从值类型(Integer,Double等)读取或写入是原子操作,因此是“线程安全的”,但在实际VB代码中这是SELDOM TRUE 。像X = Y这样的简单语句实际上并不是原子的,因为你必须在这里执行两个操作 - 首先,加载Y的值,然后设置X的值。这样的小事情让我失去了我的头发:)

无论你决定使用哪种方法,我都会发现在整个代码中通过自由评论来描述谁在什么时间可以访问哪些资源是非常宝贵的 - 从现在起三个月后,你不会记得这段代码的优点和评论真有帮助。

祝你好运!

答案 2 :(得分:1)

您可以使用Semaphore来解决生产者消费者问题:

static void Main(string[] args)
{ new Thread(Producer).Start(); new Thread(Consumer).Start(); }

static int K = 10;
static n = new Semaphore(0, K), e = new Semaphore(K, K);
static int[] buffer = new int[K];
static int _in, _out;

static void Producer()
{
    while (true)
    {
        e.WaitOne();
        buffer[_in] = produce();
        _in = (_in + 1) % buffer.Length;
        n.Release();
    }
}

static void Consumer()
{
    while (true)
    {
        n.WaitOne();
        var v = buffer[_out];
        _out = (_out + 1) % buffer.Length;
        e.Release();

        consume(v);
    }
}

答案 3 :(得分:0)

任何时候数据都可以访问多个线程,您必须编写代码以假定访问多个线程。 “墨菲定律”规定了多线程场景。

例如,如果一个线程正在填充一个槽,而另一个线程正在清空它,则需要锁定它。

答案 4 :(得分:0)

您可以将检查联锁起来,并消除任何顾虑:

 const FREE = 0;
 const FILLING = 1;
 const READY = 2;
 const DRAINIG = 3;

制片:

if (FREE == Interlocked.CompareExchange(ref slotFlag, FILLING, FREE))
{
   fillslot();
   old = Interlocked.Exchange(ref slotFlag, READY);
   Debug.Assert (FILLING == old);
}

消费者:

if (READY == Interlocked.CompareExchange(ref slotFlag, DRAINIG, READY))
{
    drain_slot();
    old = Interlocked.Exchange(ref slotFlag, FREE);
    Debug.Assert( DRAINIG == old);
 }

如果您有许多核心,许多生产者和/或许多消费者,这是无锁且非常有效的。这个问题,也就是使用bool的问题,就是没有等待语义。制作人和消费者都必须寻找免费和分别准备好的插槽。您可以通过添加“就绪”和“免费”事件来克服这个问题。此外,您需要注意确保正确维护环形缓冲区写入/读取位置。