SoFunction
Updated on 2025-03-06

A tutorial on lock deadlock in C#

There is a keyword lock in C#. Its function is to lock a certain code block and allow only one thread to access the code block at the same time. This article will talk about the principle of the lock keyword and several issues that should be paid attention to:

The prototype of lock is:

lock(X)
{
  //Codes that require locking...}

First of all, you need to understand why the above paragraph can lock the code. The mystery is the object X. In fact, X is any reference type. Its function here is that when any thread executes lock(X), X needs to be exclusive to run the following code. If it is assumed that there are now 3 threads A, B, and C that have all executed lock(X) and ABC has X at this time, then ABC will stop and queue and use X one by one, so that there is only one thread running in the following code block (because there is only one thread that shares X and the other two are queueing), so this X must be a resource that all processes that want to execute the critical area code must share, thus playing the role of suppressing threads.

Let’s talk about the problems encountered and paid attention to during lock use. One of the most important issues that lock needs to pay attention to is thread deadlock!

There are 3 typical questions listed on MSDN:

Generally, locking the public type should be avoided, otherwise the instance will be out of control of the code. Common structures lock (this), lock (typeof (MyType)) and lock ("myLock") violate this guideline:

If the instance can be accessed publicly, a lock (this) issue occurs.

If MyType can be accessed publicly, a lock (typeof (MyType)) issue will occur.

The lock("myLock") issue occurs because any other code in the process that uses the same string will share the same lock.

The best practice is to define private objects to lock, or private shared object variables to protect data shared by all instances.

(1)lock (this) Question:

Assume there are two classes:

class A{} 
class B{} 

There are two public objects:

A a=new A(); 
B b=new B(); 

First, if there is a function in A that needs to be locked:

Code 1:

lock(this)//This is a{ 
 //.... 
 lock(b) 
 { 
//...... 
 } 
} 

However, at this time, a certain function in B also has the following code that needs to be locked:

Code 2:

lock(this)//This is b{ 
 //.... 
 lock(a) 
 { 
//...... 
 } 
} 

Imagine what the consequences will be if the above two pieces of code are executed simultaneously under two threads?

As a result, after code 1 is executed to lock(this), after code 2 is executed to lock(this), b is locked, then code 1 requires b, code 2 requires a. At this time, both requirements are occupied by each other and there is a stalemate state, and the program is deadlocked.

(2) lock(typeof (MyType)) problem:

Assume there are two common variables:

int a;float b; 

See the following code below

Code 3:

lock(typeof(a))//typeof(a) is type{ 
 //.... 
 lock(typeof(b)) 
 { 
//...... 
 } 
} 

There is also the following code:

Code 4:

lock(typeof(b))//typeof(b) is type{ 
 //.... 
 lock(typeof(a)) 
 { 
//...... 
 } 
} 

If two processes enter the locks on the outer layer of the above two codes at the same time, the sum will be locked separately, and immediately they need to resonate again, possess each other, and stalemate each other, and the program enters a deadlock state!

(3) String problem:

Before explaining this issue, there is a knowledge that everyone must know: strings in C# are "standby" by the common language runtime library (CLR). This means that any given string in the entire program has only one instance, that is, this same object represents the text in all threads of all running application domains. Therefore, as long as a lock is placed on a string with the same content at any location in the application process, all instances of that string in the application will be locked.

The implication is to assume that there are two classes with two strings:

class A 
{ 
 string a="abc"; 
 string b="def"; 
} 

class c 
{ 
 string c="abc"; 
 string d="def"; 
} 

In fact, a and c refer to the same string "abc", b and d refer to the same string "def"

Now if there is the following code in the two classes

In class A there is code 5:

lock(b)//b is "def"{ 
 //.... 
 lock(a)//a is "abc" { 
//...... 
 } 
} 

In class B, there is code 6:

lock(c)//c is "abc"{ 
 //.... 
 lock(d)//d is "def" { 
//...... 
 } 
} 

Then the execution results of code 5 and code 6 are conceivable: when two threads execute to the outer lock code, "def" and "abc" are locked. Then they demand "abc" and "def" at the internal lock, and at this time the two strings are occupied by the two processes and the program is deadlocked again! So MSDN said: Locking strings is especially dangerous! It is best not to use it!

MSDN finally said: The best practice is to define private objects to lock, or private shared object variables to protect data shared by all instances.
In my personal opinion, it is not absolutely safe, so here is an example:

Assume there is a class:

class A 
{ 
 private Object a=new Object(); 
 private Object b=new Object(); 
 public void x() 
 { 
  lock(a) 
  { 
    //..... 
    lock(b) 
    { 
     //.... 
    } 
  } 
 } 
 public void y() 
 { 
  lock(b) 
  { 
    //..... 
    lock(a) 
    { 
     //.... 
    } 
  } 
 } 
} 

Now assume that two threads execute functions x() and y() at the same time; the result is that private objects a and b are locked in the outer layer respectively, and then the two threads immediately need b and a, a, b to occupy each other and demand each other, and the program is deadlocked.

So it depends on the situation, but defining private objects to lock can at least reduce the risk.

I hope that the application of lock in C# described in this article will be helpful to everyone's C# programming design.