10

I have a simple client application that receives byte buffers from the network with a low throughput. Here is the code:

private static readonly HashSet<int> _capturedThreadIds = new HashSet<int>();

private static void RunClient(Socket socket)
{
    var e = new SocketAsyncEventArgs();
    e.SetBuffer(new byte[10000], 0, 10000);
    e.Completed += SocketAsyncEventsArgsCompleted;

    Receive(socket, e);
}

private static void Receive(Socket socket, SocketAsyncEventArgs e)
{
    var isAsynchronous = socket.ReceiveAsync(e);
    if (!isAsynchronous)
        SocketAsyncEventsArgsCompleted(socket, e);
}

private static void SocketAsyncEventsArgsCompleted(object sender, SocketAsyncEventArgs e)
{
    if (e.LastOperation != SocketAsyncOperation.Receive || e.SocketError != SocketError.Success || e.BytesTransferred <= 0)
    {
        Console.WriteLine("Operation: {0}, Error: {1}, BytesTransferred: {2}", e.LastOperation, e.SocketError, e.BytesTransferred);
        return;
    }

    var thread = Thread.CurrentThread;
    if (_capturedThreadIds.Add(thread.ManagedThreadId))
        Console.WriteLine("New thread, ManagedId: " + thread.ManagedThreadId + ", NativeId: " + GetCurrentThreadId());

    //Console.WriteLine(e.BytesTransferred);

    Receive((Socket)sender, e);
}

The threading behavior of the application is quite curious:

  1. The SocketAsyncEventsArgsCompleted method is frequently run in new threads. I would have expected that after some time no new thread would be created. I would have expected the threads to be reused, because of the thread pool (or IOCP thread pool) and because the throughput is very stable.
  2. The number of threads stays low, but I can see in the process explorer that threads are frequently created and destroyed. Likewise, I would not have expected threads to be created or destroyed.

Can you explain the application behavior?

Edit: The "low" throughput is 20 messages per second (roughly 200 KB/s). If I increase the throughput to more than 1000 messages per second (50 MB/s), the application behavior does not change.

cao
  • 997
  • 9
  • 17
  • my guess, it depends on interval between data received and how long a thread is kept alive in pool ? – Parimal Raj Aug 12 '14 at 16:19
  • Same guess here - I do not think threads are kept alive in the pool forever. – Maciej Aug 12 '14 at 16:23
  • Why do you think thread creation/destruction have to do explicitly with the socket request? – Yuval Itzchakov Aug 12 '14 at 16:28
  • The application is a simple prototype with only a Main that runs `RunClient`. The thread creations and destructions can only be related to the `SocketAsyncEventArgs`. – cao Aug 13 '14 at 07:13
  • The network throughput is constant in my test, there is no activity peak. The pool should not have to create new threads. Anyway I tried to play with `SetMinThreads` and `SetMaxThreads` with no success. – cao Aug 13 '14 at 07:18
  • How many Receive calls per second are you executing? Please quantify how many new threads you are seeing. – usr Aug 13 '14 at 10:30
  • How many new threads are you seeing per second? – usr Aug 13 '14 at 13:26
  • Almost every 15 seconds, 2 threads are created and just after 2 threads are destroyed. – cao Aug 13 '14 at 16:24
  • @cao I understand that you are after an explanation, right? You don't need a solution because this is not a perf problem. Configure symbols in process explorer, run your code in 32 bit mode and look at the stacks of those threads that come and go. Are they even .NET threads? WinSock seems to sometimes have a thread or two. – usr Aug 13 '14 at 17:18
  • To be honest I am after an explanation that opens something up. I would be clearly disappointed with an answer like “This is IOCP (or WinSock) black magic and there is nothing you can do about it”, although I might have to accept it. If you are interested in performance issues related to this behavior, see http://goo.gl/2lMYVT. – cao Aug 13 '14 at 21:21
  • 1
    @cao OK it *is* a perf problem (potentially). If you are that latency-sensitive, consider just using PInvoke to call into the socket APIs. Depending on how much functionality you need this is more or less work. Or, just use synchronous IO. It uses less CPU and has less latency. It starts to break down with many threads, though. "Many" is a very wide term here. – usr Aug 13 '14 at 21:30
  • Of course, I can use synchronous IO. To be clear, `Socket.Receive` is what I am using in production code right now. But I thought that there might be a way to use `SocketAsyncEventArgs` in an allocation-free manner. Or that I might learn something meaningful about the thread pool internals :) – cao Aug 13 '14 at 21:45

2 Answers2

6

The low application throughput itself cannot explain the thread creation and destruction. The socket receives 20 messages per seconds, which is more than enough to keep a thread alive (the waiting threads are being destroyed after spending 10 seconds idle).

This problem is related to the thread pool thread injection, i.e. the threads creation and destruction strategy. Thread pool threads are regularly injected and destroyed in order to measure the impact of new threads on the thread pool throughput.

This is called thread probing. It is clearly explained in the Channel 9 video CLR 4 - Inside the Thread Pool (jump to 26:30).

It seems like thread probing is always done with newly created threads instead of moving a thread in and out of the pool. I suppose it works better like this for most applications because it avoids to keep an unused thread alive.

cao
  • 997
  • 9
  • 17
2

From MSDN

Beginning with the .NET Framework 4, the thread pool creates and destroys worker threads in order to optimize throughput, which is defined as the number of tasks that complete per unit of time. Too few threads might not make optimal use of available resources, whereas too many threads could increase resource contention.

Note

When demand is low, the actual number of thread pool threads can fall below the minimum values.

Basically it sounds like your low throughput is causing the thread pool to destroy threads since they are not required, and are just sat taking up resources. I wouldn't worry about it. As MS explicitly state:

In most cases the thread pool will perform better with its own algorithm for allocating threads.

If you're really bothered, you could always poll ThreadPool.GetAvailableThreads() to watch the pool, and see how different network throughputs affect it.

GazTheDestroyer
  • 20,722
  • 9
  • 70
  • 103
  • 2
    Thank you for pointing me to the MSDN article. I also read it. It is a good start to understand the application behavior. But your answer does not satisfy me because increasing the throughput does not remove the threading issue. – cao Aug 13 '14 at 12:46