目前,我有一个由生产者和消费者线程运行的RingBuffer。
在寻找一种有序终止它们的方法时,我想我会使用一个标志来指示生产者何时完成,然后在我的消费者中检查该标志以及需要写入的环形缓冲区槽的数量。如果生产者已经完成并且环形缓冲区没有需要写入的插槽,则消费者可以终止。
效果很好。
但是,如果我通过插入睡眠来人为地延长生产者所花费的时间,则消费者不会终止。我相信这是使用信号量的结果。
这是我正在使用的代码。请注意,在写入所有插槽后程序将“挂起”。生产者终止,但消费者“挂起”。
任何关于以有序方式终止的建议都将受到高度赞赏。
编辑 - 使用Henk建议使用队列更新了代码。 +1000指向第一个建议终止消费者/生产者线程的更好的方法知道正在使用的项目的确切数量或返回值为null / nothing 表示队列中不再存在任何项目(尽管这并不意味着它们仍未生成。)
编辑 - 我相信我已经明白了。只需为每个使用者传递null或不传递给RingBuffer.Enqueue,并捕获使用者中的null或nothing对象以终止它。希望有人觉得这很有用。
Imports System.Collections
Module Module1
Public Class RingBuffer
Private m_Capacity As Integer
Private m_Queue As Queue
Public Sub New(ByVal Capacity As Integer)
m_Capacity = Capacity
m_Queue = Queue.Synchronized(New Queue(Capacity))
End Sub
Public Sub Enqueue(ByVal value As Object)
SyncLock m_Queue.SyncRoot
If m_Queue.Count = m_Capacity Then
Threading.Monitor.Wait(m_Queue.SyncRoot)
End If
m_Queue.Enqueue(value)
Threading.Monitor.PulseAll(m_Queue.SyncRoot)
End SyncLock
End Sub
Public Function Dequeue() As Object
Dim value As Object = Nothing
SyncLock m_Queue.SyncRoot
If m_Queue.Count = 0 Then
Threading.Monitor.Wait(m_Queue.SyncRoot)
End If
value = m_Queue.Dequeue()
Console.WriteLine("Full Slots: {0} - Open Slots: {1}", m_Queue.Count, m_Capacity - m_Queue.Count)
Threading.Monitor.PulseAll(m_Queue.SyncRoot)
End SyncLock
Return value
End Function
End Class
Public Class Tile
Public buffer() As Byte
Public Sub New()
buffer = New Byte(1023) {}
End Sub
End Class
Public Sub Producer(ByVal rb As RingBuffer)
Dim enq As Integer = 0
Dim rng As New System.Security.Cryptography.RNGCryptoServiceProvider
For i As Integer = 0 To 1023
Dim t As New Tile
rng.GetNonZeroBytes(t.buffer)
rb.Enqueue(t)
enq += 1
Threading.Thread.Sleep(10)
Next i
rb.Enqueue(Nothing)
Console.WriteLine("Total items enqueued: " & enq.ToString())
Console.WriteLine("Done Producing!")
End Sub
Public Sub Consumer(ByVal rb As RingBuffer)
Dim deq As Integer = 0
Using fs As New IO.FileStream("c:\test.bin", IO.FileMode.Create)
While True
Dim t As Tile = rb.Dequeue()
If t Is Nothing Then Exit While
fs.Write(t.buffer, 0, t.buffer.Length)
deq += 1
Threading.Thread.Sleep(30)
End While
End Using
Console.WriteLine("Total items dequeued: " & deq.ToString())
Console.WriteLine("Done Consuming!")
End Sub
Sub Main()
Dim rb As New RingBuffer(1000)
Dim thrdProducer As New Threading.Thread(AddressOf Producer)
thrdProducer.SetApartmentState(Threading.ApartmentState.STA)
thrdProducer.Name = "Producer"
thrdProducer.IsBackground = True
thrdProducer.Start(rb)
Dim thrdConsumer As New Threading.Thread(AddressOf Consumer)
thrdConsumer.SetApartmentState(Threading.ApartmentState.STA)
thrdConsumer.Name = "Consumer"
thrdConsumer.IsBackground = True
thrdConsumer.Start(rb)
Console.ReadKey()
End Sub
End Module
答案 0 :(得分:0)
如果我查看消费者功能:
If rb.FullSlots = 0 And Threading.Interlocked.Read(ProducerFinished) = 0 Then
Exit While
End If
Dim t As Tile = rb.Read()
消费者可以找到rb.FullSlots = 0但是ProducerFinished = False并继续读取()。在Read()中,它等待writerSemaphore,但同时生产者可以完成并且永远不会释放writerSemaphore。
所以(至少)制作人应该采取措施让读者在减少ProducerFinished之后继续。
但是如果你将这个'Closing'逻辑移动到Ring缓冲区,我认为你会得到更好的设计。在那里,您可以将它与数据可用逻辑相结合。