'Strange infinite loop in thread in c# using network sockets
I made a little experiment where a server send continually a message to a client until a second client be connected. Testing with telnet, the first client receive the message continually, but when the second client connects, the client counter (nClients
) increments in the second client thread, but not in the first one. So with a third and a fourth clients an so on. Variable nClients
still being 1 in the first client but is incremented in the next ones.
The code:
class Program
{
volatile static int nClients = 0;
static void clientThread(object socket)
{
Socket client = (Socket)socket;
IPEndPoint ieCliente = (IPEndPoint)client.RemoteEndPoint;
Console.WriteLine("IP: {0} port: {1}", ieCliente.Address, ieCliente.Port);
nClients++;
Console.WriteLine(nClients);
using (NetworkStream ns = new NetworkStream(client))
using (StreamWriter sw = new StreamWriter(ns))
{
sw.WriteLine("Welcome");
sw.Flush();
while (nClients < 2)
{
sw.WriteLine($"Waiting clients... now {nClients} connected");
sw.Flush();
//Thread.Sleep(100);
}
sw.WriteLine($"{nClients} clients");
sw.Flush();
}
client.Close();
}
static void Main(string[] args)
{
int port = 31416;
IPEndPoint ie = null;
ie = new IPEndPoint(IPAddress.Any, port);
using (Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
s.Bind(ie);
s.Listen(5);
Console.WriteLine($"Server listening at port: {ie.Port}");
while (true)
{
Socket cliente = s.Accept();
Thread th= new Thread(clientThread);
th.Start(cliente);
}
}
}
}
If I add an Thread.Sleep()
into the loop (or even a Console.Writeline()
) it works.
Why does the first thread not detect the change of nClients
.
Actualization:
Following the feeedback recommendations, I was trying de Interlocked.Increment()
and Volatile.Read()
but the problem persist. My code again with these changes.
class Program
{
static int nClients = 0;
static void clientThread(object socket)
{
Socket client = (Socket)socket;
IPEndPoint ieCliente = (IPEndPoint)client.RemoteEndPoint;
Console.WriteLine("IP: {0} port: {1}", ieCliente.Address, ieCliente.Port);
Interlocked.Increment(ref nClients);
Console.WriteLine(Volatile.Read(ref nClients));
using (NetworkStream ns = new NetworkStream(client))
using (StreamWriter sw = new StreamWriter(ns))
{
sw.WriteLine("Welcome");
sw.Flush();
while (Volatile.Read(ref nClients) < 2)
{
sw.WriteLine($"Waiting clients... now {Volatile.Read(ref nClients)} connected");
sw.Flush();
//Thread.Sleep(100);
}
sw.WriteLine($"{Volatile.Read(ref nClients)} clients");
sw.Flush();
}
client.Close();
}
static void Main(string[] args)
{
int port = 31416;
IPEndPoint ie = null;
ie = new IPEndPoint(IPAddress.Any, port);
using (Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
s.Bind(ie);
s.Listen(5);
Console.WriteLine($"Server listening at port: {ie.Port}");
while (true)
{
Socket cliente = s.Accept();
Thread th = new Thread(clientThread);
th.Start(cliente);
}
}
}
}
And here a video showing this behavior. And a bonus: when I close abruptly the server, sometimes the client continue in the loop. I think that for some reason when I close the server, Windows close the process but the threads memory is not released and continue running.
Solution 1:[1]
The problem is here:
nClients++;
This is being executed by more than one thread simultaneously, and it is not threadsafe.
The reason why is as follows. The code is (logically) equivalent to the following:
int temp = nClients + 1; // [A]
nClients = temp; // [B]
So lets imagine that nClients
is 0. Thread #1 executes line [A] and thus temp
is 1. Now imagine that before thread #1 has executed [B], thread #2 comes along and executes [A]. Thread #2's value for temp
is also 1, because nothing has changed the value of nClients
yet.
Now both threads continue, and they both set nClients
to temp
, which is 1.
Oops! An increment went missing.
That's what's happening. (Well, this is actually a gross simplification, but it's a good way to understand how it can go wrong.)
To fix it you can either put a lock
around the incrementing code, or (simpler and more efficient) use Interlocked.Increment()
:
Interlocked.Increment(ref nClients);
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |