Vb.net .NET多线程变量访问
我有一个4线程的应用程序。(GUI、控制器、生产者、消费者) GUI是不言自明的 控制器在一些初始设置之后启动生产者和消费者线程 制作人创建项目并将其放置在“环形缓冲区”中的空闲插槽中 消费者从“环形缓冲区”中取出项目并将其写入磁盘 生产者创造物品的速度远远高于消费者 消费者是IO重的和IO绑定的 目前,我正在检查每个环形缓冲槽中的一个变量,以确定它是否可以写入 if Slot.Free then Write Slot.Data To Disk end if 如果有空的话 将插槽数据写入磁盘 如果结束 我没有使用lock/synclock,而是读取/写入插槽的“free”变量的值。我不相信这是正确的,即使它是一个易变的读/写。是否有更好的方法读取/写入此变量?变量的类型为“integer”,可以是0或1。任何时候,如果数据可以被多个线程访问,则必须编写代码,以假定它将被多个线程访问。“墨菲定律”规定了多线程场景Vb.net .NET多线程变量访问,vb.net,multithreading,Vb.net,Multithreading,我有一个4线程的应用程序。(GUI、控制器、生产者、消费者) GUI是不言自明的 控制器在一些初始设置之后启动生产者和消费者线程 制作人创建项目并将其放置在“环形缓冲区”中的空闲插槽中 消费者从“环形缓冲区”中取出项目并将其写入磁盘 生产者创造物品的速度远远高于消费者 消费者是IO重的和IO绑定的 目前,我正在检查每个环形缓冲槽中的一个变量,以确定它是否可以写入 if Slot.Free then Write Slot.Data To Disk end if 如果有空的话 将插槽数据写入磁盘
例如,如果一个线程正在填充插槽,而另一个线程正在清空插槽,则需要在其周围设置锁。有几种方法可以安全地执行此操作
- 如果您的体系结构和需求允许,您可以使用自定义事件,这样一个线程就可以简单地向另一个侦听线程发送信号,通知变量状态已更改。但是,您必须跟踪谁在消费什么事件,以及这些消费者是否对变量是只读的,或者是读/写的
- 您还可以在变量类型周围使用一个简单的自定义包装类(或使用泛型),为您锁定/解锁代码。在VB.NET中,我发现使用Monitor类锁定私有实例变量非常方便
- 互斥体和信号量-.NET有一个互斥体类和一个信号量类。这两者都有助于控制对线程共享变量的访问。我喜欢互斥体,因为它们非常容易使用,而且您不需要跟踪有多少线程可以访问给定的资源
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);
}
}
您可以通过联锁操作进行检查,消除任何问题:
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的问题,是没有等待语义。生产者和消费者都将不得不旋转寻找免费和各自准备好的插槽。您可以通过添加“就绪”和“自由”事件来克服这一问题。您还需要注意确保正确维护环形缓冲区的写入/读取位置。您提到使用环形缓冲区,但是(正确实现的)环形缓冲区能够在不检查其所有元素的情况下确定其是否已满,从而消除了在每个插槽中使用布尔值的需要 我不习惯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
实际上,由于他使用布尔值,并且只有一个生产者和消费者,所以只要您将数据和布尔值设置为正确的顺序,就不需要严格地使用锁。我并不是说这是一种方式:)这是CLR做出的保证吗?嗯,在VB.NET中似乎没有与
volatile
等价的东西。没有它,CLR在技术上可以自由更改执行顺序。我怀疑它真的会,但这不能保证。嗨。谢谢你的示例代码。我注意到所有插槽都有一个读锁和一个写锁。是否有理由不在每个插槽中设置一个读锁和一个写锁?锁的存在主要是因为需要更新\u head
和\u tail
变量。插槽的使用顺序是固定的,因此信号量足以确保插槽可以读取或写入。“s”信号量的用途是什么?信号量s
旨在确保缓冲区操作(读取/写入)不重叠。但你是对的,在这种情况下这并不是必要的,所以我相应地编辑了我的答案。谢谢