Learn the differences between multithreading and multiprocessing, and the right configuration for different types of applications. This comprehensive guide uses a relatable post office analogy to explain complex concepts, helping you optimize your Lambda functions for better performance and cost-efficiency.
In this article, we will be discussing the benefits of running application code concurrently within AWS Lambda functions.
Understanding Concurrency: Multithreading vs Multiprocessing
Concurrency is the ability to run multiple instances of the same program in parallel or seemingly in parallel. Concurrency can be achieved using a variety of methods, and most commonly it is done through multithreading, multiprocessing, or asynchronous programming. The first two methods refer to the manipulation of the computer’s CPU while asynchronous programming is more of a programming paradigm.
In this article, we will focus on multithreading and multiprocessing. To understand these two concepts, we will walk through the scenario of sending five letters at the post office.
Single-process
Now that the letters have been sent to be delivered, we turn our focus to the delivery process where a worker will deliver the letters.
In a single-process model, imagine a single delivery worker delivering each of the five letters one by one. The second letter cannot be delivered until the first is completed. Also, any new letter that gets added to the process will require more time for the additional delivery. Just like with the single-threaded scenario, this process is not efficient at all. If this were the actual delivery process, the post office would have a mailing crisis.
Code Examples to Explore Concurrency
At 1,769 MB, a function has the equivalent of one vCPU (one vCPU-second of credits per second).
For our examples, we will use Python 3.11 with 1,024 MB of memory allocated unless otherwise specified. We have two separate mock functions: one for I/O operations and one for CPU-intensive operations. For the I/O operation, the function sleeps for ten seconds. For the CPU-intensive operation, the function counts down from the INITIAL_COUNT variable, which is set to 200 million, until it hits zero.
We will specify which test to run using the Lambda console test Event JSON, for example:
{
"type": "multi_thread_io"
}
Example: Single-thread, single-process with I/O-intensive task
{
"type": "single_thread_single_process_io"
}
Cost Considerations
The grid below shows a cost breakdown based on the run-time duration of the function. For each invocation of the function, the cost breakdown is:
We can see that for both multithreading and multiprocessing, they reduce the per invocation cost for their respective I/O and CPU-intensive tasks. We see that not only do these concurrency models run their workloads faster, they also reduce costs.
Conclusion
Concurrency is a powerful tool to make your applications more efficient. The three different types of concurrency discussed in this post have specific use cases where they are beneficial. If the application is I/O intensive, a multithreaded approach is preferred so that the thread isn’t blocked by the I/O request. If the application is CPU intensive, a multiprocessing approach is preferred so that each task can be executed in parallel. And if the application is traffic intensive, the application will horizontally scale by default so that multiple instances of the function can be running in parallel.