C# 我可以更好地优化这种并发性吗?
我最近开始编写我的第一个多线程代码,希望您能给我一些建议 它从由流解析器在后台填充的缓冲区中传递视频样本(不在本问题的范围内)。如果缓冲区为空,则需要等待缓冲区级别变为可接受,然后继续 代码用于Silverlight 4,删除了一些错误检查:C# 我可以更好地优化这种并发性吗?,c#,.net,multithreading,concurrency,C#,.net,Multithreading,Concurrency,我最近开始编写我的第一个多线程代码,希望您能给我一些建议 它从由流解析器在后台填充的缓冲区中传递视频样本(不在本问题的范围内)。如果缓冲区为空,则需要等待缓冲区级别变为可接受,然后继续 代码用于Silverlight 4,删除了一些错误检查: // External class requests samples - can happen multiple times concurrently protected override void GetSampleAsync() { Inter
// External class requests samples - can happen multiple times concurrently
protected override void GetSampleAsync()
{
Interlocked.Add(ref getVideoSampleRequestsOutstanding, 1);
}
// Runs on a background thread
void DoVideoPumping()
{
do
{
if (getVideoSampleRequestsOutstanding > 0)
{
PumpNextVideoSample();
// Decrement the counter
Interlocked.Add(ref getVideoSampleRequestsOutstanding, -1);
}
else Thread.Sleep(0);
} while (!this.StopAllBackgroundThreads);
}
void PumpNextVideoSample()
{
// If the video sample buffer is empty, tell stream parser to give us more samples
bool MyVidBufferIsEmpty = false; bool hlsClientIsExhausted = false;
ParseMoreSamplesIfMyVideoBufferIsLow(ref MyVidBufferIsEmpty, ref parserAtEndOfStream);
if (parserAtEndOfStream) // No more data, start running down buffers
this.RunningDownVideoBuffer = true;
else if (MyVidBufferIsEmpty)
{
// Buffer is empty, wait for samples
WaitingOnEmptyVideoBuffer = true;
WaitOnEmptyVideoBuffer.WaitOne();
}
// Buffer is OK
nextSample = DeQueueVideoSample(); // thread-safe, returns NULL if a problem
// Send the sample to the external renderer
ReportGetSampleCompleted(nextSample);
}
代码似乎运行良好。然而,我被告知使用Thread.Wait(…)是“邪恶的”:当没有请求样本时,我的代码会不必要地循环,占用CPU时间
我的代码可以进一步优化吗?既然我的类是为一个需要样本的环境设计的,那么潜在的“无意义循环”场景是否会超过当前设计的简单性呢
非常感谢您的评论。这看起来像是经典的生产者/消费者模式。解决这个问题的通常方法是使用所谓的阻塞队列 net的4.0版为这类问题引入了一套高效、设计良好的解决方案。我想它能满足你现在的需要 如果您无法访问.NET4.0,那么许多网站都包含阻塞队列的实现。就我个人而言,我的标准参考书是乔·达菲的书。这将是一个良好的开端 使用阻塞队列的第一个优点是,您不再使用繁忙的等待循环、对
Sleep()
等的恶意调用。使用阻塞队列来避免此类代码始终是一个好主意
然而,我认为使用阻塞队列有一个更重要的好处。目前,用于生成工作项、使用它们和处理队列的代码都是混杂的。如果您正确使用阻塞队列,那么您将得到更好的分解代码,它将保留算法的各个组件:队列、生产者和消费者。这看起来像经典的生产者/消费者模式。解决这个问题的通常方法是使用所谓的阻塞队列 net的4.0版为这类问题引入了一套高效、设计良好的解决方案。我想它能满足你现在的需要 如果您无法访问.NET4.0,那么许多网站都包含阻塞队列的实现。就我个人而言,我的标准参考书是乔·达菲的书。这将是一个良好的开端 使用阻塞队列的第一个优点是,您不再使用繁忙的等待循环、对
Sleep()
等的恶意调用。使用阻塞队列来避免此类代码始终是一个好主意
然而,我认为使用阻塞队列有一个更重要的好处。目前,用于生成工作项、使用它们和处理队列的代码都是混杂的。如果您正确使用阻塞队列,那么您将得到更好的分解代码,它将保留算法的不同组件:队列、生产者和消费者。您有一个主要问题:
Thread.Sleep()
它的粒度约为20毫秒,这对于视频来说有点粗糙。此外,Sleep(0)
可能存在低优先级线程不足的问题[]
更好的方法是等待Waithandle,最好内置在队列中 您有一个主要问题:
Thread.Sleep()
它的粒度约为20毫秒,这对于视频来说有点粗糙。此外,Sleep(0)
可能存在低优先级线程不足的问题[]
更好的方法是等待Waithandle,最好内置在队列中 是阻塞队列的一个简单好例子。主键是线程需要与信号协调,而不是通过检查计数器的值或数据结构的状态。任何检查都需要资源(CPU),因此您需要信号(Monitor.Wait和Monitor.Pulse)。是阻塞队列的一个简单好例子。
主键是线程需要与信号协调,而不是通过检查计数器的值或数据结构的状态。任何检查都需要资源(CPU),因此您需要信号(Monitor.Wait和Monitor.Pulse)。您可以使用线程而不是手动线程.sleep。这样做相当简单:
AutoResetEvent e;
void RequestSample()
{
Interlocked.Increment(ref requestsOutstanding);
e.Set(); //also set this when StopAllBackgroundThreads=true!
}
void Pump()
{
while (!this.StopAllBackgroundThreads) {
e.WaitOne();
int leftOver = Interlocked.Decrement(ref requestsOutstanding);
while(leftOver >= 0) {
PumpNextVideoSample();
leftOver = Interlocked.Decrement(ref requestsOutstanding);
}
Interlocked.Increment(ref requestsOutstanding);
}
}
注意,实现信号量可能更具吸引力。大体上在您的场景中,同步开销可能几乎为零,而一个更简单的编程模型是值得的。有了信号灯,你会有这样的东西:
MySemaphore sem;
void RequestSample()
{
sem.Release();
}
void Pump()
{
while (true) {
sem.Acquire();
if(this.StopAllBackgroundThreads) break;
PumpNextVideoSample();
}
}
…我认为简单是值得的
e、 g.信号量的简单实现:
public sealed class SimpleSemaphore
{
readonly object sync = new object();
public int val;
public void WaitOne()
{
lock(sync) {
while(true) {
if(val > 0) {
val--;
return;
}
Monitor.Wait(sync);
}
}
}
public void Release()
{
lock(sync) {
if(val==int.MaxValue)
throw new Exception("Too many releases without waits.");
val++;
Monitor.Pulse(sync);
}
}
}
在一个简单的基准上,这个简单的实现需要约1.7秒,其中信号量需要7.5秒,信号量lim需要1.1秒;换句话说,非常合理。您可以使用线程而不是手动线程。睡眠。这样做相当简单:
AutoResetEvent e;
void RequestSample()
{
Interlocked.Increment(ref requestsOutstanding);
e.Set(); //also set this when StopAllBackgroundThreads=true!
}
void Pump()
{
while (!this.StopAllBackgroundThreads) {
e.WaitOne();
int leftOver = Interlocked.Decrement(ref requestsOutstanding);
while(leftOver >= 0) {
PumpNextVideoSample();
leftOver = Interlocked.Decrement(ref requestsOutstanding);
}
Interlocked.Increment(ref requestsOutstanding);
}
}
注意,实现信号量可能更具吸引力。大体上在您的场景中,同步开销可能几乎为零,而一个更简单的编程模型是值得的。有了信号灯,你会有这样的东西:
MySemaphore sem;
void RequestSample()
{
sem.Release();
}
void Pump()
{
while (true) {
sem.Acquire();
if(this.StopAllBackgroundThreads) break;
PumpNextVideoSample();
}
}
…我认为简单是值得的
e、 g.信号量的简单实现:
public sealed class SimpleSemaphore
{
readonly object sync = new object();
public int val;
public void WaitOne()
{
lock(sync) {
while(true) {
if(val > 0) {
val--;
return;
}
Monitor.Wait(sync);
}
}
}
public void Release()
{
lock(sync) {
if(val==int.MaxValue)
throw new Exception("Too many releases without waits.");
val++;
Monitor.Pulse(sync);
}
}
}
在一个简单的基准上,这个简单的实现需要约1.7秒,其中信号量需要7.5秒,信号量lim需要1.1秒;换句话说,非常合理。Thread.Sleep(0);->睡眠(10);有一个
Interlocked.Increment()
和Interlocked.Decrement()
:)@Eugene我把你的评论理解为Thread.Sleep(0)也可以是Thread.Sleep(10),因为它至少会受到惩罚10ms@Carlos永远不要使用Thread.Sleep(0),因为它没用——它会极大地利用你的CPU。设置实际延迟的最佳方法-它将减少CPU消耗,尤其是在do/while情况下。@Eugene,@TimWi:Thread.Sleep(0);->睡眠(10);有