Task multi-threading (.NET)?

What the problem is in the code? How to modify your code to avoid it?

using System;
using System.Threading;

namespace Mover
{
 internal class Endpoint
{
 public int Amount { get; set; }
}

 internal class Program
{
 private static void Main(string[] args)
{
 var source = new Endpoint();
 var target = new Endpoint();

 var initialAmount = 1000000;
 source.Amount = initialAmount;

 var thread = new Thread(new ThreadStart(delegate
{
 Transfer(source, target, initialAmount);
}));
thread.Start();
 Transfer(target, source, initialAmount / 2);
thread.Join();

 Console.Out.WriteLine("source.Amount = {0}", source.Amount);
 Console.Out.WriteLine("the target.Amount = {0}", target.Amount);
}

 private static void Transfer(Endpoint source, Endpoint target, int count)
{
 while (count-- > 0)
 lock (target)
 lock (source)
{
source.Amount--;
target.Amount++;
}
}
}
}
October 10th 19 at 09:16
2 answers
October 10th 19 at 09:18
Solution
Can get a deadlock if the main thread executes one of the lock-s, and then control is given to another thread.
Be avoided in several ways (here the task is very muddy, the goal is unclear, so it is difficult to say how)
1. make the shared object lock
2. to make the lock in the setter of properties Amount
3. lozitsa to objects in a particular sequence; for example, you can enter the id of the objects
And is there a solution to this?

 while (count-- > 0)
{
 lock (target)
{
target.Amount++;
}
 lock (source)
{
source.Amount--;
}
}
- will_Lang commented on October 10th 19 at 09:21
Yes. this is similar to my option #2, just there is a little code duplication - Zakary74 commented on October 10th 19 at 09:24
make a reservation: setter I was not referring to the Amount { set; }, and an atomic operation, something like IncrementAmount. - Zakary74 commented on October 10th 19 at 09:27
I was told that the decision "introduces the problem of visible inconsistentname condition." However, I did not specify what was meant. - will_Lang commented on October 10th 19 at 09:30
Yes, enter, but the problem about it was said. Have in mind that the bytes read from the target, but not recorded at source, at the time, as another thread is reading from source and writing to target. If you are interested in it as the grease in this sense, then need to make a total lock. - Zakary74 commented on October 10th 19 at 09:33
Yeah, now I understand. Thanks for the clarification! - will_Lang commented on October 10th 19 at 09:36
But where is the DeadLock? Because the lock is captured in one place and in the same order. If you are one of the locks and the thread will lose the quantum, another lock in another thread can't be taken, because initially the thread was supposed to pick the first lock. - Letha_Runolfsso commented on October 10th 19 at 09:39
No, it seems that in the same order, because the method says "eigse lock; target lock;"
And the methods-the objects come in different order - Zakary74 commented on October 10th 19 at 09:42
So lock source can be obtained only after lock acquired target because the receipt is in the critical section. Isn't it? - Letha_Runolfsso commented on October 10th 19 at 09:45
All, I apologize for the careless and did not notice that in the methods the parameters are transmitted in different sequences. - Letha_Runolfsso commented on October 10th 19 at 09:48
Look: suppose one thread has taken the lock on the target, after which it was interrupted. The second at this time took the lock on source and reached the place lock (target). Failed because lock is busy, and gave control to the first. And at first this manual lock (source). And this failed, because the source is already occupied. That's all. - Zakary74 commented on October 10th 19 at 09:51
oops, have not updated the comments. yeah, the ambush that the arguments are referred to as objects themselves, I also did not notice at first - Zakary74 commented on October 10th 19 at 09:54
October 10th 19 at 09:20
Will propose a solution:
 public class Endpoint
{
 private Mutex _sendLock;
 private Mutex _receiveLock;

 private int _amount;
 public int Amount
{
get
{
 return _amount;
}
}

 public Endpoint(int amount)
{
 _sendLock = new Mutex();
 _receiveLock = new Mutex();

 _amount = amount;
}

 public static void Transmit(Endpoint source, Endpoint, destination, int amount)
{
 WaitHandle[] transmittingPartiesHandles = new WaitHandle[] { source._sendLock, destination._receiveLock };
WaitHandle.WaitAll(transmittingPartiesHandles);

 // Do smth.

 Interlocked.Decrement(ref source._amount);
 Interlocked.Increment(ref destination._amount);

source._sendLock.ReleaseMutex();
destination._receiveLock.ReleaseMutex();
}
}


public class Program
{
 public static void Main(string[] args)
{
 Detroit Endpoint = new Endpoint(1000000);
 London Endpoint = new Endpoint(0);

 int amount = detroit.Amount;

 Thread thread = new Thread(() => Transfer(detroit, london, amount));

thread.Start();
 Transfer(london, detroit, amount / 2);
thread.Join();

 Console.Out.WriteLine("source.Amount = {0}", detroit.Amount);
 Console.Out.WriteLine("the target.Amount = {0}", london.Amount);
Console.ReadLine();
}

 private static void Transfer(Endpoint source, Endpoint target, int amount)
{
 for (int i = 0; i < amount; i++)
{
 Endpoint.Transmit(source, target, amount);
}
}
}


First — do the Endpoint object itself is thread-safe. In fact to alter the Amount of external code.
Second — get rid of locks on real objects (this is bad because it can exist alongside another scenario in which for absolutely other purposes to again take a Lok to the same object Endpoint)
In the third — allocated transfer process in a separate method. General lock will not work here if there are multiple gears in different locations (there will be a lot of objects "shared" lock on the same pair of endpoints for which there are problems).

Find more questions by tags multithreading.NETC#