Saturday, September 21, 2024

The Most Powerful Concepts in Programming Languages: Concurrency



In this blog, I would like to introduce the concepts that I believe are the most powerful in programming languages, and mastering them will give you solid programming thinking. I will explain them one by one with examples in Java and Python. After introducing the concepts of object orientation and recursion in the first two articles, I will elucidate the concept of concurrency in this article.



1. Object-oriented
2. Recursion

3. Concurrency

4. Functional Programming and Lambda Expressions
5. Data Structure
6. Software Design Patterns
7. Annotations/Decorators
8. Machine Learning



1. What is Concurrency?

Concurrency is the ability of a program to perform multiple tasks simultaneously. Concurrency promotes efficient use of resources and increases program responsiveness and performance. However, concurrency is challenging due to potential synchronization issues. Tasks running in parallel are not entirely independent of each other. They often share data and modify it.


Changing the shared data by multiple tasks simultaneously can corrupt data and lead to inconsistent states or problems such as deadlocks. That’s why you should define synchronization mechanisms to ensure correctness and avoid such issues.


Many programming languages such as Java or Python allow concurrent programming with various concepts, models, and the corresponding syntax. Java provides mechanisms for thread management and synchronization. On the other hand, Python also has threading and multiprocessing modules.


Concepts such as thread, task, and process are commonly used in concurrent programming. Here are some common approaches to concurrency that you should master to get ready to develop concurrent programs.


Process: It is an independent program instance that executes in a separate memory space. A process uses its own resources. It can run concurrently with other processes and communicate with them. Known mechanisms allowing inter-process communication are message passing, pipes, and shared memory.


Thread: It is an execution unit or a worker inside a process. Threads of the same process share its memory space. They can run concurrently, communicate, and synchronize with each other.  


Task: it is a piece of work to be done. You can use a thread to accomplish a task.


Asynchronous Programming: It is a programming model that enables your program to run tasks concurrently without waiting for each other. During the execution of the tasks, the program is not blocked and is ready to respond to other events. Asynchronous programming is very useful in event-driven systems or I/O-bound applications.


Parallelism: When we talk about concurrency, we say that tasks run simultaneously, but this is still theoretical. From an implementation perspective, the situation is slightly different. It is not the case of true concurrency when multiple tasks are running on a single processor. The processor will use the shared time to run one task at a time. 


We speak of true concurrency or parallelism when multiple tasks are executed on multiple processors or cores. In a distributed system, the tasks are carried out by several processors or computers at the same time. That is much more efficient. Thanks to the enormous advancement and availability of hardware, parallelism became affordable.


Concurrency Control: As mentioned earlier, concurrent tasks share resources. They often access and modify the same variables, making their values unpredictable. That is where concurrency control comes to the rescue. 

It is the procedure that provides mechanisms for managing shared resources between concurrent tasks. Techniques such as locks, semaphores, and atomic operations are defined and used to guide and administer access to the shared resources and avoid conflicts.


2. Example of Concurrency in Java

We will demonstrate in this section a concurrent program in Java using a small example of threads. For this reason, we first define a sub-class of the ‘Thread’ class as follows:


public class MyThread extends Thread {

    private String name;

    private int step;

    private int sleepTime;


    public MyThread(String name, int step, int sleepTime) {

        this.name = name;

        this.step = step;

        this.sleepTime =  sleepTime;

    }


    @Override

    public void run() {

        System.out.println(name + " - Start");

        for (int index = 0; index < step; index++) {

            System.out.println(" > " + name + " - Step: " + index);

            try {

                Thread.sleep(sleepTime);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

        System.out.println(name + " - End");

    }

}


The ‘MyThread’ class must extend the ‘Thread’ Java class to run concurrently. We declare three fields ‘name’ (thread name), ‘step’ (loop execution number), and ‘sleepTime’ (waiting time). ‘MyThread’ class needs to replace the ‘run’ method with new code to perform the actual work. In our case, it’s simply a code where we display the start and the code of the task. Then we run a loop between them ‘step’ times. The statement inside this loop consists of printing a message. For each execution of the loop, we wait for ‘sleepTime’, and continue the execution.


Let’s take a simple example of creating and starting instances of ‘MyThread’ in Java:


public class ThreadExample {

    public static void main(String[] args) {

        System.out.println("Main Thread - Start");

        // Create two threads

        Thread thread1 = new MyThread("Thread 1", 4, 500);

        Thread thread2 = new MyThread("Thread 2", 4, 700);

        // Start the execution of the two threads

        thread1.start();

        thread2.start();

        // Main thread finished its execution

        System.out.println("Main Thread - End");

    }

}


In the main method of the ‘ThreadExample’ class, we create two instances of the ‘MyThread’ class. We can distinguish them by their names. After that, we call the ‘start’ method for each one of them. That means the ‘run’ method is executed for each thread. Both threads and the main thread run concurrently. Let’s inspect the output of executing this code:


Main Thread - Start

Main Thread - End

Thread 2 - Start

Thread 1 - Start

 > Thread 1 - Step: 0

 > Thread 2 - Step: 0

 > Thread 1 - Step: 1

 > Thread 2 - Step: 1

 > Thread 1 - Step: 2

 > Thread 2 - Step: 2

 > Thread 1 - Step: 3

Thread 1 - End

 > Thread 2 - Step: 3

Thread 2 - End


According to the result of the code, the threads  ‘Thread 1’ and ‘Thread 2’, and the main thread run concurrently and are independent of each other. The main thread completes before the two threads even begin.


3. Example of Concurrency in Python

Python allows concurrent programming and supports the concept of ‘Thread’. In this example, we show how to create and start threads in Python by using the ‘threading’ module:


import threading

import time


def my_thread_func(thread_name, step, sleep_time):

    for i in range(step):

        print(f"{thread_name} - Start")

        print(f"{thread_name} - Step: {i}")

        time.sleep(sleep_time)

        print(f"{thread_name} - End")


print("Main Thread - Start")

# Create two threads

thread1 = threading.Thread(target=my_thread_func, args=("Thread 1", 3, 1))

thread2 = threading.Thread(target=my_thread_func, args=("Thread 2", 3, 1))

# Start the threads

thread1.start()

thread2.start()

# Main thread continues its execution

print("Main Thread - End")


In this example, we create a method ‘my_thread_func’ that implements dummy code and prints information such as the thread name at runtime to track of the order of execution. Then we create two objects of the ‘Thread’ class defined in the ‘threading’ module. We pass the ‘my_thread_func’ method with the necessary parameters as the code of each thread.


Next, we use the ‘start’ method to start the threads, which triggers the execution of the target function for each thread. These two threads and the main thread run concurrently and independently of each other. The execution of this code gives us the following result:


Main Thread - Start

Thread 1 - Start


Thread 1 - Step: 0

Thread 2 - StartMain Thread - End

Thread 2 - Step: 0


Thread 2 - End

Thread 1 - EndThread 2 - Start

Thread 2 - Step: 1


Thread 1 - Start

Thread 1 - Step: 1

Thread 1 - End

Thread 1 - Start

Thread 1 - Step: 2

Thread 2 - End

Thread 2 - Start

Thread 2 - Step: 2

Thread 2 - EndThread 1 – End

No comments:

Post a Comment

Blog Posts

Enhancing Performance of Java-Web Applications

Applications built with a Java back-end, a relational database (such as Oracle or MySQL), and a JavaScript-based front-end form a common and...