Difference in Concurrency Aspects in Python
One may imagine that Python only just introduced these notions or capabilities, given that we’re hearing a lot of new trends regarding asynchronous operations and concurrency with the release of Python 3.
Many newcomers might believe using asyncio
is the only practical approach to performing concurrent and asynchronous activities. This article will discuss how we can achieve concurrency and its benefits or drawbacks in Python.
Threads and Multithreading
Threads have been in Python for a very long time. As a result, we may perform multiple operations at once thanks to threads.
Unfortunately, CPython
, a typical mainline Python version, still uses the global interpreter lock (GIL
), which makes multi-threaded applications—common nowadays’s method of implementing parallel processing—less than ideal.
Python introduced GIL
to make CPython’s memory handling more manageable integrations with C (for example, the extensions).
The GIL
is a locking mechanism in that the Python interpreter runs only one thread simultaneously. Python’s byte
code can only ever be executed by one thread simultaneously.
Example Code:
import threading
import time
import random
def worker(num):
sec = random.randrange(1, 5)
time.sleep(sec)
print("I am thread {}, who slept for {} seconds.".format(num, sec))
for i in range(3):
t = threading.Thread(target=worker, args=(i,))
t.start()
print("Completed!")
Output:
Completed!
I am thread 1, who slept for 3 seconds.
I am thread 3, who slept for 2 seconds.
I am thread 4, who slept for 4 seconds.
Processes and Multiprocessing
Multiprocessing makes use of many CPUs. We can effectively conduct several tasks simultaneously since each CPU operates in parallel. For jobs that are CPU-bound, multiprocessing is what you want to use.
Python introduces the multiprocessing
module to achieve parallelism, which will feel very similar if you have used threading.
Example Code:
import multiprocessing
import time
import random
def worker(num):
sec = random.randrange(1, 5)
time.sleep(sec)
print("I am process {}, who slept for {} seconds.".format(num, sec))
for i in range(3):
t = multiprocessing.Process(target=worker, args=(i,))
t.start()
print("Completed")
Output:
Completed
I am process 1, who slept for 1 seconds.
I am process 2, who slept for 2 seconds.
I am process 0, who slept for 3 seconds.
Instead of multithreading, we are using multiple processes running on different cores of your CPU, which makes our Python script faster.
Asynchronous and asyncio
In synchronous operations, the tasks are carried out in sync, one after the other. However, jobs may begin in asynchronous operations entirely independently of one another.
One async
task might start and continue to run while the execution switches to another activity. On the other hand, asynchronous tasks often execute in the background and don’t block (make the implementation wait for completion).
Along with other valuable features, asyncio
offers an event loop. The event loop monitors various I/O events, switches to ready tasks, and pauses tasks awaiting I/O.
As a result, we don’t waste time on unfinished projects.
Example Code:
import asyncio
import datetime
import random
async def my_sleep_func():
await asyncio.sleep(random.randint(0, 5))
async def displayDate(num, loop):
endTime = loop.time() + 60.0
while True:
print("Loop: {} Time: {}".format(num, datetime.datetime.now()))
if (loop.time() + 1.0) >= endTime:
break
await my_sleep_func()
loop = asyncio.get_event_loop()
asyncio.ensure_future(displayDate(1, loop))
asyncio.ensure_future(displayDate(2, loop))
loop.run_forever()
If we walk through the snippet of code above:
- We have an
async
functiondisplayDate
, which takes a number and the event loop as parameters. - The said function has an infinite loop that stops after 60 seconds. But during these 60 secs, it repeatedly prints out the time and takes a nap.
- The
await
function can wait on otherasync
functions to complete. - We pass the function to the event loop (using the
ensure_future
function). - We start running the event loop.
Whenever the await
call is made, asyncio
understands that the function will probably need some time. When asyncio
notices that halted function’s I/O is ready, it resumes the process.
Now, the point is, what do we need to use among the three forms of concurrency? We can take note of the following to help in our decision-making:
- Use multiprocessing for CPU Bound operations.
- Use multithreading for I/O Bound, Fast I/O, and Limited Number of Connections.
- Use Asynchronous IO for I/O Bound, Slow I/O, and many connections.
asyncio/await
works on Python 3.5 and later.
We can also refer to the pseudocode below:
if io_bound:
if io_very_slow:
print("Use asyncio")
else:
print("Use multithreading")
else:
print("multiprocessing")
Marion specializes in anything Microsoft-related and always tries to work and apply code in an IT infrastructure.
LinkedInRelated Article - Python Threading
- How to Get a Return Value From a Thread in Python
- Difference Between Multiprocessing and Threading in Python
- Timer Class in the Threading Module in Python
- Python Threadpool