Software Construction Assignment 3
Software Construction Assignment 3
Concurrency
Name CMS ID
Asna Maqsood 426990
Muhammad Owais Khan 404262
Umar Farooq 406481
Zainab Athar 405094
Concurrency refers to a system's ability to execute multiple tasks at the same time, enhancing
performance, responsiveness, and efficient resource use. It is implemented through approaches like
parallelism, multitasking, and multithreading. This document delves into how concurrency is achieved
across ten different types of software systems, supported by practical examples and relevant case studies.
Web Servers
Web servers are designed to handle millions of client requests simultaneously, making
concurrency an essential feature for their operation. This capability ensures that users experience
minimal delays even during high-traffic periods. Let's explore how concurrency is achieved in web
servers through multithreading and the event-driven model, supported by examples and code
snippets.
Multithreading
Multithreading allows web servers to allocate a separate thread for each incoming client request. This
enables parallel processing, reducing wait times and improving responsiveness.
How it Works:
Each thread processes an individual request independently.
Threads share common resources (like memory), but proper synchronization ensures thread
safety.
Ideal for servers where individual requests involve blocking operations like file I/O or database
queries.
app = Flask(__name__)
def handle_request(client_id):
# Simulate processing time
print(f"Processing request from Client {client_id}")
import time
time.sleep(2)
3|Page
print(f"Completed request from Client {client_id}")
@app.route('/process', methods=['GET'])
def process_request():
client_id = request.args.get('client_id', 'unknown')
thread = threading.Thread(target=handle_request, args=(client_id,))
thread.start()
return f"Request from Client {client_id} is being processed!"
if __name__ == '__main__':
app.run(threaded=True) # Enable multithreading
Event-Driven Model
The event-driven model is another efficient way to achieve concurrency. Instead of using a thread for each
request, it uses a single thread with non-blocking I/O. This approach is lightweight and highly scalable, as
seen in servers like Nginx and Node.js.
How it Works:
A single thread manages multiple connections using an event loop.
Non-blocking I/O operations allow the thread to handle other requests while waiting for resources
(e.g., file read/write).
Suitable for I/O-heavy applications like serving static files or REST APIs.
server.listen(3000, () => {
console.log('Server is listening on port 3000');
4|Page
});
Example
Consider a scenario where a user requests a webpage that includes images, JavaScript files, and CSS. The
web server:
Assigns separate threads (in multithreading) or events (in event-driven) to process each file
request.
Responds with each file as soon as it is processed, ensuring the browser can render the webpage
progressively.
Apache HTTP Server: Uses both multithreading and process-based models for handling concurrent
requests.
Nginx: Implements an event-driven architecture to handle thousands of simultaneous connections
efficiently.
Node.js: Leverages a single-threaded event loop with asynchronous callbacks for non-blocking I/O.
5|Page
Database Systems
Concurrency in database systems ensures that multiple queries or transactions can be executed at the
same time without compromising data consistency, accuracy, or isolation. This capability is critical in multi-
user environments, where simultaneous access to data is common. Let's explore the mechanisms that
enable concurrency, supported by coding examples and tools.
Transaction Management
Transaction management involves controlling the execution of multiple transactions to ensure data
consistency and avoid conflicts. Concurrency control protocols, such as locking mechanisms and
timestamp-based protocols, play a vital role.
Key Techniques:
Locking Protocols:
o Shared Locks: Allow multiple transactions to read data simultaneously.
o Exclusive Locks: Prevent other transactions from accessing data during updates.
Timestamp-Based Protocols:
o Transactions are assigned timestamps to ensure sequential execution order and prevent
conflicts.
-- Transaction 1
START TRANSACTION;
SELECT balance FROM accounts WHERE account_id = 1 FOR UPDATE; -- Exclusive lock
UPDATE accounts SET balance = balance - 500 WHERE account_id = 1;
COMMIT;
-- Transaction 2
START TRANSACTION;
SELECT balance FROM accounts WHERE account_id = 1 FOR UPDATE; -- Waits until Transaction 1 is
committed
UPDATE accounts SET balance = balance + 500 WHERE account_id = 2;
COMMIT;
Isolation Levels
Isolation levels determine how transactions interact when executed concurrently. These levels define the
trade-off between consistency and performance:
1. READ UNCOMMITTED: Allows dirty reads, where a transaction reads uncommitted changes from
another transaction.
6|Page
2. READ COMMITTED: Prevents dirty reads but allows non-repeatable reads (data changes during a
transaction).
3. REPEATABLE READ: Prevents dirty reads and non-repeatable reads but allows phantom reads (new
rows added by other transactions).
4. SERIALIZABLE: Ensures full isolation by serializing transactions, preventing all anomalies.
Example
In a banking system, concurrency control ensures that two users transferring money from the same
account do not create conflicts:
Scenario:
User A tries to transfer $500 from Account 1.
User B simultaneously tries to transfer $300 from Account 1.
7|Page
COMMIT;
Modern database management systems (DBMS) provide built-in mechanisms for concurrency:
MySQL: Offers transaction management with InnoDB storage engine and supports
multiple isolation levels.
PostgreSQL: Implements advanced locking and MVCC (Multi-Version Concurrency Control)
for high concurrency.
Oracle DB: Features robust concurrency control with locking mechanisms and snapshot
isolation.
Microsoft SQL Server: Supports row-level locking and transaction isolation levels.
8|Page
Batch Processing Systems
Batch processing systems are designed to handle and process extensive datasets in grouped tasks called
batches. These systems operate without user interaction, making them ideal for scenarios like payroll
processing, report generation, and data analysis.
Concurrency in batch processing systems is crucial for efficient resource utilization and faster execution. It
is achieved using various techniques:
Task Scheduling:
Tasks are divided into smaller, independent units that can execute concurrently. This division ensures that
the system can process multiple tasks simultaneously across available resources.
Example:
In a payroll processing system, each employee's payroll computation is treated as an independent task,
which can be scheduled and executed in parallel.
def compute_payroll(employee_id):
print(f"Processing payroll for employee {employee_id}")
# Simulate computation
return f"Payroll computed for employee {employee_id}"
if __name__ == "__main__":
employees = [101, 102, 103, 104, 105]
Parallel Execution:
Modern batch processing systems use distributed frameworks like Apache Hadoop and Apache Spark to
execute tasks in parallel across a cluster of nodes.
# Show results
employee_df.show()
Output:
Name Salary Bonus
John 1000 100.0
Alice 1200 120.0
Bob 900 90.0
Jane 1100 110.0
Map Function:
"public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable> {" (“MapReduce-
Demo/src/main/java/mapReduceTest/wordCount/WordCount ... - GitHub”)
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
StringTokenizer itr = new StringTokenizer(value.toString());
10 | P a g e
while (itr.hasMoreTokens()) {
word.set(itr.nextToken());
context.write(word, one);
}
}
}
Reduce Function:
public static class IntSumReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable result = new IntWritable();
public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException,
InterruptedException { (“WordCount.java - GitHub”)
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
result.set(sum);
context.write(key, result);
}
}
Example:
In a payroll processing system, tasks such as tax calculation, bonus computation, and payment generation
are processed in parallel. Each task runs independently on distributed nodes, significantly reducing
processing time.
11 | P a g e
Microservices Architecture
Microservices architecture is a design approach where applications are split into smaller, independent
services. Each service performs a specific business function, operates as an independent process, and
communicates with other services through APIs or messaging. This architectural style offers scalability,
flexibility, and resilience.
Concurrency in microservices is achieved through the independence of services and their ability to process
tasks simultaneously. Key techniques include:
Independent Services:
Each microservice runs as an independent process, allowing for concurrent execution of multiple services.
This approach ensures that services can operate autonomously, and scale independently based on their
workload.
All these services can run concurrently, processing their respective tasks without waiting for one another.
12 | P a g e
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
Message Queuing:
Asynchronous messaging systems like RabbitMQ and Apache Kafka facilitate communication between
services without blocking execution. This enables non-blocking interactions, ensuring services remain
responsive.
def send_message():
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
# Publish a message
message = "New order received"
channel.basic_publish(exchange='', routing_key='order_queue', body=message)
print(f"Sent: {message}")
connection.close()
if __name__ == "__main__":
send_message()
def receive_message():
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
# Consume messages
channel.basic_consume(queue='order_queue', on_message_callback=callback, auto_ack=True)
13 | P a g e
print('Waiting for messages...')
channel.start_consuming()
if __name__ == "__main__":
receive_message()
import java.util.Properties;
producer.close();
}
}
14 | P a g e
import java.util.Collections;
import java.util.Properties;
while (true) {
ConsumerRecords<String, String> records = consumer.poll(100);
for (ConsumerRecord<String, String> record : records) {
System.out.printf("Received event: %s%n", record.value());
}
}
}
}
Example:
A food delivery app may have independent microservices for order management, payment, and
notifications, all running concurrently.
15 | P a g e
Operating Systems
Operating systems (OS) play a critical role in enabling multitasking and managing the concurrent execution
of processes and threads. They ensure the efficient allocation of resources, such as CPU time, memory, and
I/O devices, enabling multiple applications or tasks to run simultaneously. Concurrency in operating
systems is essential for improving system responsiveness, resource utilization, and overall performance.
Operating systems achieve concurrency through several techniques, including process and thread
management, scheduling algorithms, and interrupt handling. Let's explore these mechanisms in more
detail.
Process Scheduling
To manage multiple processes, the OS uses scheduling algorithms to determine the order in which
processes receive CPU time. Some common scheduling algorithms include:
Round-Robin (RR): Allocates a fixed time slice for each process in a cyclic order.
Shortest Job Next (SJN): Selects the process with the shortest execution time next.
Priority Scheduling: Executes processes based on their priority levels.
Thread Scheduling
Thread scheduling works similarly, but it deals with scheduling multiple threads within a single process.
The OS can run threads concurrently, with each thread performing a part of the program’s work.
Interrupt Handling:
An interrupt is a mechanism that allows the operating system to respond to immediate, real-time events,
typically from hardware devices. When an interrupt occurs, the OS temporarily suspends the current
process and transfers control to a special function called an interrupt handler or interrupt service routine
(ISR). This ensures the system responds to high-priority tasks, such as handling input from a keyboard or
mouse.
Interrupt handling is crucial for achieving concurrency because it allows the OS to switch between tasks
quickly and efficiently.
int main() {
// Set up the interrupt handler for SIGINT (Ctrl+C)
signal(SIGINT, handle_interrupt);
return 0;
}
17 | P a g e
Example:
When you open multiple applications on your computer, the operating system manages each application
as a separate process. The OS uses scheduling algorithms to allocate CPU time to each process, allowing
them to run concurrently. For instance:
1. Opening a web browser may involve running processes like chrome.exe or firefox.exe.
2. Opening a word processor runs word.exe as another process.
3. Opening a music player starts a third process.
All these processes run independently, and the OS schedules CPU time for each, so they appear to execute
concurrently, even though there is only one CPU (in the case of single-core CPUs). With multi-core CPUs,
multiple processes can run in parallel, further improving performance.
Linux: Linux provides process scheduling algorithms, thread management, and support for real-
time interrupt handling.
Windows: Windows OS supports process scheduling and thread management, along with kernel-
level support for interrupts.
macOS: macOS, based on Unix, provides similar process and thread management, along with
efficient handling of interrupts.
Operating systems like Linux, Windows, and macOS offer built-in facilities for handling process scheduling,
thread management, and interrupt handling, ensuring efficient multitasking and optimal resource
utilization.
18 | P a g e
Distributed Systems
Distributed systems consist of multiple independent nodes (computers or servers) that work together to
achieve a shared goal. These systems are inherently designed to handle tasks across various locations,
providing scalability, fault tolerance, and high availability. Concurrency plays a critical role in distributed
systems, as it allows tasks to be processed in parallel across multiple nodes, improving performance and
resource utilization.
Distributed systems achieve concurrency through techniques like distributed computation, parallel
processing, and consensus algorithms. These mechanisms enable the system to handle multiple tasks
concurrently, ensuring data consistency, fault tolerance, and coordination among different nodes.
Distributed Computation:
In distributed systems, tasks are divided into smaller, independent sub-tasks that can be processed
concurrently across different nodes. These nodes communicate with each other over a network to
exchange data and synchronize their activities. By distributing computation, a distributed system can
process vast amounts of data more efficiently and at a larger scale.
Consensus Algorithms:
In distributed systems, especially in scenarios where nodes store data across multiple servers or databases,
maintaining consistency is crucial. Consensus algorithms are used to ensure that multiple nodes agree on a
19 | P a g e
shared state, even in the presence of faults. These algorithms play a vital role in ensuring the correctness
and reliability of the distributed system.
class RaftNode:
def __init__(self, id):
self.id = id
self.state = "follower"
self.votes = 0
def start(self):
print(f"Node {self.id} started as {self.state}.")
while True:
if self.state == "follower":
# Simulating election timeout
time.sleep(random.uniform(1, 3))
self.state = "candidate"
self.votes = 1
print(f"Node {self.id} became a candidate and started election.")
self.elect_leader()
def elect_leader(self):
# Simulating voting process in Raft
if random.choice([True, False]): # Random decision to vote
print(f"Node {self.id} voted for a leader.")
self.votes += 1
if self.votes > 2: # Assume 3 nodes for simplicity
self.state = "leader"
print(f"Node {self.id} became the leader.")
Example:
Consider a scenario where a distributed database system partitions data across multiple nodes. When a
query is executed, each node handles a portion of the query, and the results are combined to produce the
final output.
Apache Kafka: Kafka is a distributed messaging system used to handle large-scale data streaming.
It ensures data consistency and supports concurrent processing.
Apache ZooKeeper: ZooKeeper is a service that helps coordinate distributed systems, ensuring
synchronization and consensus among distributed nodes.
Hadoop HDFS: The Hadoop Distributed File System (HDFS) partitions large datasets across multiple
nodes and provides a mechanism for concurrent data processing.
21 | P a g e
Video/Graphics Rendering Systems
Video and graphics rendering, especially in the context of 3D animation and high-definition video
production, requires significant computational power. With the increasing complexity of rendering tasks,
achieving concurrency is essential to reduce rendering times and improve the efficiency of graphical
processing systems. This is particularly relevant in industries like animation, gaming, and virtual reality,
where the rendering of detailed scenes or complex animations must be done in real time or as efficiently
as possible.
Video rendering systems achieve concurrency through mechanisms like task parallelism, GPU acceleration,
and distributed rendering. These methods allow for the simultaneous processing of multiple components
of a rendering task, speeding up the overall rendering process.
Task Parallelism:
In rendering systems, task parallelism refers to the ability to break down a rendering job into smaller,
independent tasks, each of which can be processed concurrently. For example, rendering each frame of a
video or animation independently allows for tasks to be executed in parallel. Each frame can be handled as
a separate computational task, drastically reducing the total time needed for rendering.
def render_video(total_frames):
# Create a pool of processes to render the frames concurrently
with multiprocessing.Pool(processes=4) as pool:
pool.map(render_frame, range(total_frames))
22 | P a g e
GPU Acceleration:
GPU acceleration leverages the massive parallel processing power of modern Graphics Processing Units
(GPUs), which are optimized for tasks that can be parallelized, such as rendering, image processing, and
simulations. GPUs contain thousands of small cores that can work simultaneously on different parts of a
graphical computation, making them ideal for accelerating video and graphics rendering tasks.
In the context of rendering, GPUs can process multiple pixels or vertices in parallel, allowing complex
scenes to be rendered much faster than relying solely on the Central Processing Unit (CPU). GPUs are also
used for tasks like texture mapping, lighting calculations, and shading, all of which can be executed
concurrently.
int main() {
int width = 1920;
int height = 1080;
int image_size = width * height * sizeof(int);
int *d_image;
23 | P a g e
dim3 numBlocks((width + 15) / 16, (height + 15) / 16);
// Clean up
cudaFree(d_image);
delete[] h_image;
return 0;
}
Distributed Rendering:
In large-scale rendering projects, such as those used in Hollywood studios or for high-end visual effects,
distributed rendering is often employed. This approach distributes the rendering process across multiple
machines in a network, allowing for the concurrent processing of frames or parts of frames. Distributed
rendering is commonly used in cloud-based rendering services where computational resources can be
scaled on demand.
Example:
Rendering a 3D animation may involve processing each frame concurrently to reduce the total rendering
time.
Blender: Blender is an open-source 3D rendering software that supports both CPU and GPU-based
rendering. It can also be used in a network rendering setup.
Unity and Unreal Engine: Both engines support distributed rendering and GPU acceleration for
real-time rendering in video games.
CUDA: CUDA is widely used for GPU-accelerated rendering tasks in various software systems.
24 | P a g e
Simulation Software
Simulation software is used to model real-world processes and systems, such as traffic flow, weather
patterns, and financial markets. These systems often require the execution of multiple scenarios or
simulations at once to predict outcomes under different conditions. To achieve optimal performance,
concurrency is employed to run several simulation components in parallel, reducing the time required to
process complex, computationally intensive tasks.
The primary ways concurrency is achieved in simulation software are through parallel processing and
multi-agent systems. These mechanisms allow simulation tasks to be divided into smaller, independent
units that can run concurrently, significantly improving efficiency and scalability.
Parallel Processing:
Parallel processing involves breaking a simulation task into smaller units or sub-tasks that can be executed
simultaneously across multiple processors or cores. This is especially useful in simulations that involve
complex calculations, such as those required for physical systems or large-scale data models. By utilizing
parallelism, simulations can be performed much faster, allowing for the exploration of more scenarios
within a shorter time.
def simulate_traffic(total_vehicles):
# Create a pool of processes to simulate vehicle movements concurrently
with multiprocessing.Pool(processes=4) as pool:
pool.map(simulate_vehicle, range(total_vehicles))
25 | P a g e
Multi-Agent Systems:
In multi-agent simulations, independent agents (representing entities such as vehicles, pedestrians, or
animals) operate concurrently, interacting with one another in a shared environment. Each agent has its
own behavior and decision-making logic, which can be modeled and executed in parallel. This approach is
particularly useful for simulating complex systems that involve multiple interacting components, such as in
the case of smart city simulations, ecological models, or robotic systems.
def simulate_traffic_agents(total_agents):
threads = []
for i in range(total_agents):
# Create a new thread for each agent
thread = threading.Thread(target=vehicle_agent, args=(i,))
threads.append(thread)
thread.start()
Example:
Simulating traffic flow in a city requires concurrent execution of movements for each vehicle.
26 | P a g e
Tools and Technologies:
Many simulation platforms have built-in support for concurrency, allowing users to model and run complex
simulations with ease. Some of the commonly used tools include:
MATLAB: MATLAB offers parallel computing tools, such as the parfor loop, which allows users to
execute iterations of loops concurrently on multiple workers.
Simulink: A MATLAB-based tool that enables model-based design and simulation of multi-domain
systems. It includes support for parallel execution of simulation tasks.
These tools allow users to take advantage of multiple cores or distributed computing resources to run
simulations more efficiently.
27 | P a g e
Real-Time Communication Systems
Real-time communication systems, such as video calling applications, require efficient handling of multiple
streams of data simultaneously. These systems must ensure low latency, high reliability, and real-time
performance, all of which are made possible through concurrency. By processing different media streams
(e.g., video, audio, and data) concurrently, these systems ensure smooth communication even under high
network traffic or heavy resource usage conditions.
Real-time communication systems use a combination of multithreaded processing and asynchronous I/O
to achieve concurrency. These mechanisms allow for the simultaneous handling of multiple data streams,
ensuring the responsiveness and stability of the system.
Multithreaded Processing:
In real-time communication systems, each media stream (such as audio, video, and text data) is processed
on separate threads. This allows the system to handle multiple streams concurrently without blocking
other processes. For example, while the video stream is being captured and displayed, the audio stream
can be processed and transmitted concurrently.
By assigning each of these tasks to separate threads, the system can process them in parallel without
blocking the other threads, ensuring smooth and uninterrupted communication.
28 | P a g e
for i in range(5):
print("Processing chat message...")
time.sleep(0.4) # Simulating message processing time
Example:
In a real-time communication system, audio and video data are constantly transmitted to and received
from other participants. Using asynchronous I/O, the system can send and receive data concurrently
without waiting for the transmission to complete, maintaining the app’s responsiveness.
Several technologies support the development of real-time communication systems, making it easier to
implement concurrency:
WebRTC (Web Real-Time Communication): A technology that provides browsers with the ability
to communicate in real time using simple APIs, allowing for video, voice, and data sharing.
gRPC (Google Remote Procedure Call): A high-performance, open-source and universal RPC
framework that supports bidirectional streaming, perfect for real-time communication systems.
SIP (Session Initiation Protocol): A protocol used for initiating, maintaining, and terminating real-
time sessions in video calling and VoIP applications.
These tools and technologies help developers build efficient, low-latency, and scalable real-time
communication systems.
30 | P a g e
Containers and Virtualization Systems
Containers and virtualization systems have revolutionized the way we manage and deploy applications by
allowing multiple isolated environments to run concurrently on a single physical machine. These
technologies provide efficient resource usage, scalability, and isolation, making them ideal for running
diverse applications on the same infrastructure.
Containerization:
Containerization involves running applications and their dependencies in isolated environments known as
containers. Containers share the host operating system's kernel but run as independent processes,
providing process-level isolation. This isolation ensures that each container has its own file system,
networking, and process space, allowing for concurrent execution of multiple containers on the same
machine without interference.
Containers use lightweight virtualization to achieve concurrency, allowing for quick startup times and
minimal overhead compared to traditional virtual machines. Docker is one of the most popular
containerization platforms that facilitates the deployment of applications in containers .
Hypervisors:
Virtualization systems use hypervisors to manage multiple virtual machines (VMs) running on a physical
machine. A hypervisor creates and manages VMs, each of which runs its own operating system (OS) and
applications. This provides complete isolation between VMs, allowing for concurrent execution of multiple
VMs on a single physical host.
31 | P a g e
Microsoft Hyper-V).
Type 2 Hypervisor (Hosted): It runs as an application on top of a host operating system (e.g.,
VMware Workstation, VirtualBox).
Both types of hypervisors facilitate the concurrent execution of multiple VMs, each of which can run
different operating systems and applications.
Example:
Running multiple Docker containers on a single server allows each containerized application to run
independently and concurrently.
Docker: A platform for building and running containerized applications. Docker simplifies container
management by providing a suite of tools for creating, managing, and orchestrating containers.
Kubernetes: A powerful container orchestration tool that automates deployment, scaling, and
management of containerized applications. It enables containers to be distributed across multiple
nodes, achieving high availability and fault tolerance.
VMware & VirtualBox: Virtualization platforms that allow the creation and management of virtual
machines. They provide hardware-level isolation and are useful for running multiple operating
systems on a single physical host.
AWS EC2 & Google Compute Engine: Cloud-based virtualization platforms that offer scalable
compute resources in the form of virtual machines. These platforms enable you to run multiple
VMs or containers concurrently in the cloud.
32 | P a g e
33 | P a g e