Synchronizing data in multithreading

Multiple threads can invoke the methods or properties of an object, which can make the state of an object invalid. It is possible to make conflicting changes regarding two or more threads on the same object. This makes it important to synchronize these calls, which will allow us to avoid such issues. When the members of a class are protected from conflicting changes, they are known to be thread-safe.

The CLR provides multiple ways in which we can synchronize access to the object instance and static members:

  • Synchronize code regions
  • Manual synchronization
  • Synchronize context
  • Thread-safe collection

By default, there is no synchronization for objects, which means any thread can access methods and properties at any time.

Synchronizing code regions allows us to synchronize blocks of code, methods, and static methods. However, synchronizing static fields is not supported. Synchronizing is possible if we use a Monitor class or a keyword. C# supports the lock keyword, which can be used to mark blocks of code for synchronization.

When applied, the threads attempt to acquire the lock while executing the code. If another thread has already been acquired by the lock on this block, then the thread blocks until the lock is available. The lock is released when the thread has executed the code block or exits in any other way.

MethodImplAttribute and MethodImplOptions.Synchronized give us the same results as using Monitor or keywords to lock the code block.

Let's look at an example to understand lock statements with tasks. We will learn more about tasks in the upcoming sections.

For the purpose of this example, we created an Account class that synchronizes its private field balance amount by locking it to an instance. This ensures that no two threads update this field at the same time:

 internal class BankAcc
{
private readonly object AcountBalLock = new object();
private decimal balanceamount;
public BankAcc(decimal iBal)
{
balanceamount = iBal;
}
public decimal Debit(decimal amt)
{
lock (AcountBalLock)
{
if (balanceamount >= amt)
{
Console.WriteLine($"Balance before debit :{balanceamount,5}");
Console.WriteLine($"Amount to debit :{amt,5}");
balanceamount = balanceamount - amt;
Console.WriteLine($"Balance after debit :{balanceamount,5}");
return amt;
}
else
{
return 0;
}
}
}
public void Credit(decimal amt)
{
lock (AcountBalLock)
{
Console.WriteLine($"Balance before credit:{balanceamount,5}");
Console.WriteLine($"Amount to credit :{amt,5}");
balanceamount = balanceamount + amt;
Console.WriteLine($"Balance after credit :{balanceamount,5}");
}
}
}

The TestLockStatements() method looks as follows:

//Create methods to test this Account class
public static void TestLockStatements()
{
var account = new BankAcc(1000);
var tasks = new Task[2];
for (int i = 0; i < tasks.Length; i++)
{
tasks[i] = Task.Run(() => UpdateAccount(account));
}
Task.WaitAll(tasks);
}

private static void UpdateAccount(BankAcc account)
{
var rnd = new Random();
for (int i = 0; i < 10; i++)
{
var amount = rnd.Next(1, 1000);
bool doCredit = rnd.NextDouble() < 0.5;
if (doCredit)
{
account.Credit(amount);
}
else
{
account.Debit(amount);
}
}
}

We are creating two tasks, and each task invokes UpdateMethod. This method loops 10 times and updates the account balance using either credit or debit methods. Because we are using the lock(obj) field at the instance level, the balance amount field won't be updated at the same time.

The following code shows the desired output:

Balance before debit : 1000
Amount to debit : 972
Balance after debit : 28
Balance before credit: 28
Amount to credit : 922
Balance after credit : 950
Balance before credit: 950
Amount to credit : 99
Balance after credit : 1049
Balance before debit : 1049
Amount to debit : 719
Balance after debit : 330
Balance before credit: 330
Amount to credit : 865
Balance after credit : 1195
Balance before debit : 1195
Amount to debit : 962
Balance after debit : 233
Balance before credit: 233
Amount to credit : 882
Balance after credit : 1115
Balance before credit: 1115
Amount to credit : 649
Balance after credit : 1764
Balance before credit: 1764
Amount to credit : 594
Balance after credit : 2358
Balance before debit : 2358
Amount to debit : 696
Balance after debit : 1662
Balance before credit: 1662
Amount to credit : 922
Balance after credit : 2584
Balance before credit: 2584
Amount to credit : 99
Balance after credit : 2683
Balance before debit : 2683
Amount to debit : 719
Balance after debit : 1964
Balance before credit: 1964
Amount to credit : 865
Balance after credit : 2829
Balance before debit : 2829
Amount to debit : 962
Balance after debit : 1867
Balance before credit: 1867
Amount to credit : 882
Balance after credit : 2749
Balance before credit: 2749
Amount to credit : 649
Balance after credit : 3398
Balance before credit: 3398
Amount to credit : 594
Balance after credit : 3992
Balance before debit : 3992
Amount to debit : 696
Balance after debit : 3296
Press any key to exit.

Accessing shared variables across multiple threads may cause data integrity issues. Such issues can be addressed by using a synchronization primitive. These are derived by the System.Threading.WaitHandle class. While performing manual synchronization, a primitive can protect access to shared resources. Different synchronization primitive instances are used to protect access to a resource or some parts of code access, which allows multiple threads to access a resource concurrently.

The System.Collections.Concurrent namespace was introduced by .NET Framework and can be used without additional synchronization in the user code. This namespace includes several collection classes that are both thread-safe and scalable. This allows multiple threads to add or remove items from these collections.

More information on these thread-safe collections can be found at https://docs.microsoft.com/en-us/dotnet/standard/collections/thread-safe/index.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.135.107