An asynchronous program behaves differently by taking one execution step at a time. The difference is that the system may not wait for an execution step to be completed before moving on to the next one.
This means that the program will move on to future execution steps even though a previous step hasn't yet finished and is still running elsewhere.
This also means that the program knows what to do when a previous step does finish running.
Also A synchronous program is executed one step at a time. Even with conditional branching, loops and function calls, you can still think about the code in terms of taking one execution step at a time. When each step is complete, the program moves on to the next one.
The paradigm of Asynchronous programming comes to the rescue in such cases. Asynchronous programs do not wait for a code to execute, but jumps on the other task and start executing it (well, not always but you get the idea.
Asynchronous рrоgrаmming is а fоrm оf раrаllel соmрuting in whiсh а unit оf tаsk runs s
Here at LinuxAPT, as part of our Server Management Services, we regularly help our Customers to perform Python related queries.
In this context, we shall look into how to program in an Asynchronous manner.
Here, we will look into Multiprocessing & Multithreading approaches.
Usually, applications run a single process for performing any task, thus called uniprocessor systems or applications.
However, the mighty python provides us an option for multiple processes running in parallel. What does it mean?
Think about having two python terminals open and running code on them, execution on one terminal can be done without waiting for the other:
from multiprocessing import Process
import os
import time
import random
def getProcessInfo(title):
print(title)
print('module name:', __name__)
print('parent process:', os.getppid())
print('process id:', os.getpid())
print('\n')
def printOne(n: int = 10):
getProcessInfo('printOne Function Info')
for _ in range(n):
time.sleep(random.randint(0, 3))
print("One")
def printTwo(n: int = 10):
getProcessInfo('printTwo Function Info')
for _ in range(n):
time.sleep(random.randint(0, 3))
print("Two")
if __name__ == "__main__":
procs = []
# Create a Process for printOne function
proc = Process(target=printOne, args=(10,))
# Append the Process to the list of all Processes
procs.append(proc)
#Start the Process
proc.start()
# Create a Process for printTwo function
proc = Process(target=printTwo)
# Append the Process to the list of all Processes
procs.append(proc)
# Start the Process
proc.start()
# Wait until all the processes are executed
for proc in procs:
proc.join()
Here we have created two functions that print a string whenever they are run. If we check the output, we can confirm that they are run independently and do not wait for one another.
Consider we have a dual-core computer each is assigned a core. The methods are run independently in those cores.
These multiрrосessоrs shаre the соmрuter bus, CPU сlосk, memоry, аnd the рeriрherаl deviсes.
(If there are more processes than cores, it is a different story and we shall cover that separately.)
Multiple processes sound cool, but sadly they are not very optimal every time we need to do something in parallel.
We know our CPUs have cores, which act as virtual individual CPUs. These virtual CPUs have threads in them. They handle the execution of sequences of instructions in our program.
The name hopefully makes sense right? Running multiple threads aka. multithreading:
from threading import Thread
import os
import time
import random
def getProcessInfo(title):
print(title)
print('module name:', __name__)
print('parent process:', os.getppid())
print('process id:', os.getpid())
print('\n')
# will run on Thread 1
def printOne(n: int = 10):
getProcessInfo('printOne Function Info')
for _ in range(n):
time.sleep(random.randint(0, 3))
print("One")
# will run on Thread 2
def printTwo(n: int = 10):
getProcessInfo('printTwo Function Info')
for _ in range(n):
time.sleep(random.randint(0, 3))
print("Two")
if __name__ == "__main__":
threads = []
# Create a Thread for printOne function
thread = Thread(target=printOne, args=(10,))
# Append the Thread to the list of all Threads
threads.append(thread)
thread.start()
# Create a Thread for printTwo function
thread = Thread(target=printTwo)
# Append the Thread to the list of all Threads
threads.append(thread)
thread.start()
# Wait until all the threads are executed
for thread in threads:
thread.join()
Notice it is the same process for both the threads.
Here we have a single process which in-turn has multiple threads. Code, data, and files are shared among threads and each thread has a stack.
This article covers how to make applications performant and efficiently use CPU cycles and threads. However, it is not all rainbows and unicorns when talking about asynchronous code.
Asynchronous programming is a type of parallel programming in which a unit of work is allowed to run separately from the primary application thread. When the work is complete, it notifies the main thread about completion or failure of the worker thread.
There are numerous benefits to using it, such as improved application performance and enhanced responsiveness.
On the other hand, A synchronous program is executed one step at a time. Even with conditional branching, loops and function calls, you can still think about the code in terms of taking one execution step at a time. When each step is complete, the program moves on to the next one.
Examples of synchronous program:
1. Batch processing programs are often created as synchronous programs. You get some input, process it, and create some output. Steps follow one after the other until the program reaches the desired output. The program only needs to pay attention to the steps and their order.
2. Command-line programs are small, quick processes that run in a terminal. These scripts are used to create something, transform one thing into something else, generate a report, or perhaps list out some data. This can be expressed as a series of program steps that are executed sequentially until the program is done.