Locking is essential in parallel programs. It restricts code from being executed by more than one thread at the same time. Exclusive locking is used to ensure that only one thread can enter a particular section of code at a time.
The simplest way to use synchronization in c# is with the lock
keyword. The lock
keyword works by marking a block of code as a critical section by obtaining a mutual exclusion lock for an object running a statement and then releasing the lock.
In this recipe, we are going to create a class that represents a bank account. An object of this class will be shared by a couple of parallel tasks that will be making a series of withdrawals for random amounts. The critical section of code in the Withdraw
method that updates the balance of the shared account object will be protected by a lock
statement.
Let's go to Visual Studio 2012 and take a look at the following steps on how to use mutual exclusion locks:
LockExample
as the Solution name.Account.cs
.Account
class:using System.Text; using System.Threading.Tasks;
double
to the Account
class to store the balance of the account and a private object that will be used for locking.private double _balance; private object _locker = new object();
Account
class. This constructor should accept a parameter of type double
and should initialize the balance
field.public Account(double initialBalance){
_balance = initialBalance;
}
Withdraw
method for the account. If the account has a negative balance, the Withdraw
method should throw an error. Otherwise, the Withdraw
method should obtain a mutual exclusion lock on the Account
object and deduct the requested amount from the balance.public double Withdraw(double amount) { if (_balance < 0) throw new Exception("Account has a negative balance."); } lock (_locker) { if (_balance >= amount) { Console.WriteLine("Starting balance : " + _balance); Console.WriteLine("Withdraw amount : -" + amount); _balance = _balance - amount; Console.WriteLine("Current balance : " + _balance); return amount; } else { return 0; } } }
Program
class. Make sure to add the following code snippet using directives that are at the top of the Program
class:using System; using System.Threading.Tasks;
DoTransactions
method for the Program
class. The DoTransactions
method should loop ten times doing a withdrawal of a random amount.static void DoTransactions(Account account) { Random r = new Random(); for (int i = 0; i < 10; i++) { account.Withdraw((double)r.Next(1, 100)); } }
Main
method of the Program
class, let's create a shared account object and two tasks that will concurrently execute the withdrawals. Finish up by waiting for the user input before exiting.static void Main(string[] args) { Account account = new Account(1000); Task task1 = Task.Factory.StartNew(() => DoTransactions(account)); Task task2 = Task.Factory.StartNew(() => DoTransactions(account)); Console.ReadLine(); }
The lock
keyword is a c# language shortcut for using the System.Threading
.Monitor
class. Basically, the lock
keyword ensures that threads cannot enter a critical section of code while another thread is in the critical section; the following is the code contained in the scope of the lock
statement:
lock (this) { //This is the critical section }
If a thread tries to enter a locked section of code, it will block and wait until the locked object is released. The lock will be released when the locking thread exits the scope of the lock. The lock
keyword calls System.Threading.Monitor.Enter
at the start of the scope and System.Threading.Monitor.Exit
at the end of the scope.
Notice that we created a private lockable object to lock on instead of locking on the instance of the Account
class. This is the best practice. In general, you should avoid locking on a public type or on instances of objects that are beyond your code's control. If another programmer locks your class to synchronize their data, a deadlock can occur. A deadlock is a situation in which two or more competing threads are waiting for each other to finish their work and release a lock, and thus neither one ever does. Note also that locks can only be obtained on reference types.
18.227.24.60