<jupyter_start><jupyter_text>Speed Up Your Python Program With Concurrency    Table of Contents * [What Is Concurrency?](what_is_concurrency?)* [What Is Parallelism?](what_is_parallelism?)* [When Is Concurrency Useful?](when_is_concurrency_useful?)* [How to Speed Up an I/O-Bound Program](how_to_speed_up_an_i/o-bound_program)    * [Synchronous Version](synchronous_version)--- If you’ve heard lots of talk about `asyncio` [being added to Python](https://realpython.com/python37-new-features/) but are curious how it compares to other concurrency methods or are wondering what concurrency is and how it might speed up your program, you’ve come to the right place. **In this article, you’ll learn the following:** - What **concurrency** is- What **parallelism** is- How some of **Python’s concurrency methods** compare, including `threading`, `asyncio`, and `multiprocessing`- **When to use concurrency** in your program and which module to use This article assumes that you have a basic understanding of Python and that you’re using at least version 3.6 to run the examples. You can download the examples from the [*Real Python* GitHub repo](https://github.com/realpython/materials/tree/master/concurrency-overview).  What Is Concurrency? The dictionary definition of concurrency is simultaneous occurrence. In Python, the things that are occurring simultaneously are called by different names (thread, task, process) but at a high level, they all refer to a sequence of instructions that run in order. I like to think of them as different trains of thought. Each one can be stopped at certain points, and the CPU or brain that is processing them can switch to a different one. The state of each one is saved so it can be restarted right where it was interrupted. You might wonder why Python uses different words for the same concept. It turns out that threads, tasks, and processes are only the same if you view them from a high level. Once you start digging into the details, they all represent slightly different things. You’ll see more of how they are different as you progress through the examples. Now let’s talk about the simultaneous part of that definition. You have to be a little careful because, when you get down to the details, only `multiprocessing` actually runs these trains of thought at literally the same time. [`Threading`](https://realpython.com/intro-to-python-threading/) and `asyncio` both run on a single processor and therefore only run one at a time. They just cleverly find ways to take turns to speed up the overall process. Even though they don’t run different trains of thought simultaneously, we still call this concurrency. The way the threads or tasks take turns is the big difference between `threading` and `asyncio`. In `threading`, the operating system actually knows about each thread and can interrupt it at any time to start running a different thread. This is called [pre-emptive multitasking](https://en.wikipedia.org/wiki/Preemption\\_%28computing%29Preemptive\\_multitasking) since the operating system can pre-empt your thread to make the switch. Pre-emptive multitasking is handy in that the code in the thread doesn’t need to do anything to make the switch. It can also be difficult because of that “at any time” phrase. This switch can happen in the middle of a single Python statement, even a trivial one like `x = x + 1`. `Asyncio`, on the other hand, uses [cooperative multitasking](https://en.wikipedia.org/wiki/Cooperative\\_multitasking). The tasks must cooperate by announcing when they are ready to be switched out. That means that the code in the task has to change slightly to make this happen. The benefit of doing this extra work up front is that you always know where your task will be swapped out. It will not be swapped out in the middle of a Python statement unless that statement is marked. You’ll see later how this can simplify parts of your design.  What Is Parallelism? So far, you’ve looked at concurrency that happens on a single processor. What about all of those CPU cores your cool, new laptop has? How can you make use of them? `multiprocessing` is the answer. With `multiprocessing`, Python creates new processes. A process here can be thought of as almost a completely different program, though technically they’re usually defined as a collection of resources where the resources include memory, file handles and things like that. One way to think about it is that each process runs in its own Python interpreter. Because they are different processes, each of your trains of thought in a multiprocessing program can run on a different core. Running on a different core means that they actually can run at the same time, which is fabulous. There are some complications that arise from doing this, but Python does a pretty good job of smoothing them over most of the time. Now that you have an idea of what concurrency and parallelism are, let’s review their differences, and then we can look at why they can be useful: |Concurrency Type|Switching Decision|Number of Processors||:--|:--|:--||Pre-emptive multitasking (`threading`)|The operating system decides when to switch tasks external to Python.|1||Cooperative multitasking (`asyncio`)|The tasks decide when to give up control.|1||Multiprocessing (`multiprocessing`)|The processes all run at the same time on different processors.|Many| Each of these types of concurrency can be useful. Let’s take a look at what types of programs they can help you speed up.  When Is Concurrency Useful? Concurrency can make a big difference for two types of problems. These are generally called CPU-bound and I/O-bound. I/O-bound problems cause your program to slow down because it frequently must wait for [input/output](https://realpython.com/python-input-output/) (I/O) from some external resource. They arise frequently when your program is working with things that are much slower than your CPU. Examples of things that are slower than your CPU are legion, but your program thankfully does not interact with most of them. The slow things your program will interact with most frequently are the file system and network connections. Let’s see what that looks like:  In the diagram above, the blue boxes show time when your program is doing work, and the red boxes are time spent waiting for an I/O operation to complete. This diagram is not to scale because requests on the internet can take several orders of magnitude longer than CPU instructions, so your program can end up spending most of its time waiting. This is what your browser is doing most of the time. On the flip side, there are classes of programs that do significant computation without talking to the network or accessing a file. These are the CPU-bound programs, because the resource limiting the speed of your program is the CPU, not the network or the file system. Here’s a corresponding diagram for a CPU-bound program:  As you work through the examples in the following section, you’ll see that different forms of concurrency work better or worse with CPU-bound and I/O-bound programs. Adding concurrency to your program adds extra code and complications, so you’ll need to decide if the potential speed up is worth the extra effort. By the end of this article, you should have enough info to start making that decision. Here’s a quick summary to clarify this concept: |I/O-Bound Process|CPU-Bound Process||:--|:--||Your program spends most of its time talking to a slow device, like a network connection, a hard drive, or a printer.|You program spends most of its time doing CPU operations.||Speeding it up involves overlapping the times spent waiting for these devices.|Speeding it up involves finding ways to do more computations in the same amount of time.| You’ll look at I/O-bound programs first. Then, you’ll get to see some code dealing with CPU-bound programs.  How to Speed Up an I/O-Bound Program Let’s start by focusing on I/O-bound programs and a common problem: downloading content over the network. For our example, you will be downloading web pages from a few sites, but it really could be any network traffic. It’s just easier to visualize and set up with web pages.  Synchronous Version We’ll start with a non-concurrent version of this task. Note that this program requires the [`requests`](http://docs.python-requests.org/en/master/) module. You should run `pip install requests` before running it, probably using a [virtualenv](https://realpython.com/python-virtual-environments-a-primer/). This version does not use concurrency at all:<jupyter_code>import requests
import time
def download_site(url, session):
    with session.get(url) as response:
        print(f"Read {len(response.content)} from {url}")
def download_all_sites(sites):
    with requests.Session() as session:
        for url in sites:
            download_site(url, session)
sites = [
    "https://www.jython.org",
    "http://olympus.realpython.org/dice",
] * 80
start_time = time.time()
download_all_sites(sites)
duration = time.time() - start_time
print(f"Downloaded {len(sites)} in {duration} seconds")<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.05844939138063384, 'fraction_numerical': 0.0014255949117227766, 'mean_word_length': 4.67517112632234, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 8, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '1', 'dir': 'jupyter-structured-clean-dedup', 'id': '361271', 'license': 'MIT', 'n_tokens_mistral': 2224, 'n_tokens_neox': 2159, 'n_words': 1339, 'path': '01. Python/04. Advanced/07.1 Speed Up Your Python Program With Concurrency.ipynb', 'repo_name': 'rostamimahdi1997/CS-Tutorial', 'size': '9119'}