4

I'm writing an audio application that has multiple threads producing sound and one thread that mixes the sounds and sends them to the sound card. I've tried several ways of synchronizing threads the 'right' way including Signals and thread safe queues but they were all too slow. So now I use a bool for each producer to indicate whether its queue is full. It seems to work very well (5ms latency for 32 threads) but is it safe to do it this way?

class PlayThreadParameters
{
    public Queue<Samples> queue;
    public bool isOutputQueueFull;
}

The producers look like this:

  public void PolyPlayThread(object o)
    {
        var playThreadParameters = (PlayThreadParameters)o;
        while (isPlaying)
        {
            while (playThreadParameters.isOutputQueueFull)
            {
                if (!isPlaying)
                    return;
                Thread.Sleep(1);
            }

        ... //fill output queue

        playThreadParameters.isOutputQueueFull = true;
    }
}

The consumer looks like this (called from a separate thread by Naudio):

public override int Read(byte[] array, int offset, int count)
        {

                for (int v = 0; v < playThreadParameters.Length; v++)
                    while (!playThreadParameters[v].isOutputQueueFull)
                    {
                        if (!isPlaying)
                            return 0;
                        Thread.Sleep(1); 
                    }

                ... //mix the samples from the outputqueues

                for (int v = 0; v < playThreadParameters.Length; v++)
                    playThreadParameters[v].isOutputQueueFull =false;

            return count;
        }
Erwin J.
  • 587
  • 1
  • 5
  • 15

2 Answers2

5

As far as I know, the .NET memory model doesn't guarantee that the changes of a variable made in one thread will be visible in another thread. You need a memory barrier there. The simplest (though not the most efficient) way to organize that is by using lock or Interlocked methods.

By the way, busy waiting is not the best method to achieve your goal. Maybe you'd like to switch to the producer-consumer model with appropriate condition variable (Monitors in C# parlance) usage?

Community
  • 1
  • 1
Vlad
  • 35,022
  • 6
  • 77
  • 199
4

No it is not completely safe, but you might get lucky most of the time ;-) You should be using the Interlocked methods to access the bool.

Chris O
  • 5,017
  • 3
  • 35
  • 42
  • 1
    +1 I was going to suggest the interlocked methods as well. The variable should also be marked `volatile`. – casablanca Nov 15 '10 at 01:04
  • Unfortunately Interlocked is not available in .net 3.5. But isn't setting a bool atomic? – Erwin J. Nov 15 '10 at 01:22
  • @casablanca Thanks that seems to be exactly what i needed – Erwin J. Nov 15 '10 at 01:23
  • 1
    @Erwin J.: `volatile` still doesn't guarantee atomicity with multiple cores - each core can have its own cache which isn't flushed except through an interlocked instruction. – casablanca Nov 15 '10 at 01:26
  • @Erwin: Interlocked is available since .NET 1.1 – BrokenGlass Nov 15 '10 at 01:27
  • 1
    Interlocked is also available for 3.5. Reading/writing a 32-bit or smaller value is atomic, but the reason you need Interlocked (and possibly volatile) is because of out of order instruction execution, the JIT, OS, and the processor itself can all execute instructions out of order, all according to various rules. If thread 1 on CPU 1 is writing to your bool, then later thread 2 on CPU 2 is reading, it may be possible that thread 2 will see a stale value for your bool, unless you use the Interlocked. – Chris O Nov 15 '10 at 01:30
  • Thanks a lot. I'm using Interlocked.Exchange to set the flags and Interlocked.CompareExchange to read them. It's slightly slower than volatile but much faster than lock, monitor or signal. I found this answer useful too http://stackoverflow.com/questions/2484980/why-is-volatile-not-considered-useful-in-multithreaded-c-or-c-programming – Erwin J. Nov 15 '10 at 14:26
  • 1
    @Erwin J. thanks for the link to the other question. Here's another article for you on the exciting topic of false sharing, http://msdn.microsoft.com/en-us/magazine/cc872851.aspx, just in case you bump into this problem. – Chris O Nov 15 '10 at 14:53
  • @Chris O That's a great article. I've added some padding and CPU usage went down by 2-4%. – Erwin J. Nov 15 '10 at 22:05