Intra-process synchronization and inter-process synchronization are distinct methods for controlling access to shared resources, each functioning at varying levels.
Intra-process Synchronization
Intra-process synchronization involves the coordination of shared resources within a single process. It requires multiple threads within the same process to manage access to shared data or resources to avoid race conditions and maintain data integrity. Various methods for intra-process synchronization in C# include lock (Monitor), Mutex, Semaphore, SemaphoreSlim, and ReaderWriterLockSlim.
Example Scenario 1
Consider a web application where multiple threads handle incoming HTTP requests and need to read from or write to a shared in-memory cache.
internal class SharedInMemoryCache
{
private readonly object lockObject = new object();
private Dictionary<int, string> cache = new Dictionary<int, string>();
public string GetItem(int key)
{
lock (lockObject)
{
if (cache.TryGetValue(key, out string value))
{
return value;
}
return null;
}
}
public void AddItem(int key, string value)
{
lock (lockObject)
{
cache[key] = value;
}
}
}
Example Scenario 2
Within this situation, there exists a customer information database in which various threads (representing distinct users) execute read and write tasks on the customer data. It is imperative to synchronize these tasks to prevent race conditions and uphold data integrity.
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedResourceMechanism
{
internal class CustomerInformation
{
private readonly object lockObject = new object();
public void ReadCustomerDetails(int customerId)
{
lock (lockObject)
{
using (SqlConnection connection = new SqlConnection("your_connection_string")) // Write your connection string here
{
connection.Open();
SqlCommand command = new SqlCommand("SELECT * FROM Customers WHERE CustomerID = @CustomerID", connection);
command.Parameters.AddWithValue("@CustomerID", customerId);
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
// Proceed with writing the code to process the data.
}
}
// The lock will be released in this area.
}
}
public void WriteCustomerDetails(int customerId, string name)
{
lock (lockObject)
{
using (SqlConnection connection = new SqlConnection("your_connection_string")) // Write your connection string here
{
connection.Open();
SqlCommand command = new SqlCommand("UPDATE Customers SET Name = @Name WHERE CustomerID = @CustomerID", connection);
command.Parameters.AddWithValue("@CustomerID", customerId);
command.Parameters.AddWithValue("@Name", name);
command.ExecuteNonQuery();
}
// The lock will be released in this area.
}
}
}
}

Explanation of the above diagrammatic flow.
1. A new CustomerInformation Database instance is instantiated
Three threads (t1, t2, t3) are initialized to execute read and write operations on the CustomerInformation database.
2. Thread Initialization
Threads t1, t2, and t3 are launched simultaneously.
3. Thread Tasks
- Thread t1 executes ReadCustomerDetails.
- Thread t2 executes WriteCustomerDetails.
- Thread t3 executes ReadCustomerDetails.


Mariusz PostolPosted Jul 31, 2024, 1:17 PM
Do you address the critical section problem? A critical section refers to a portion of a program where multiple concurrent threads access and modify shared resources (such as variables, data structures, or devices). The critical section must be executed atomically, meaning that only one thread can be inside at any time. This ensures data consistency and prevents race conditions. Do you agree that synchronization is just replacing concurrent programming with sequential programming inside the critical section?