我有一个包含4个线程的应用程序。 (GUI,Controller,Producer,Consumer)
GUI不言自明。
控制器在一些初始设置后启动生产者和消费者线程。
制作人创建项目并将其放置在“环形缓冲区”的空闲槽中
消费者从“环形缓冲区”中获取项目并将它们写入磁盘。
生产者以比消费者高得多的速度创造物品。
消费者IO重,IO限制。
目前我正在检查每个环形缓冲区槽中的变量,以确定它是否可以写入。
if Slot.Free then Write Slot.Data To Disk end if
我没有使用lock / synclock,而只是读取/写入插槽的“free”变量的值。即使它是一个易失性的读/写,我也不相信这是正确的。有没有更好的方法来读/写这个变量?变量的类型为“整数”,为0或1。
答案 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)
有几种方法可以安全地完成这项工作。
请注意,尽管某些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的问题,就是没有等待语义。制作人和消费者都必须寻找免费和分别准备好的插槽。您可以通过添加“就绪”和“免费”事件来克服这个问题。此外,您需要注意确保正确维护环形缓冲区写入/读取位置。