Hint: You can adjust the default video playback speed in your account settings.
Hint: You can set the default subtitles language in your account settings.
Sorry! Looks like there’s an issue with video playback 🙁 This might be due to a temporary outage or because of a configuration issue with your browser. Please see our video player troubleshooting guide to resolve the issue.

Two Threads

Give Feedback

In this lesson, you’ll create and start your first thread. If you download the sample code, you can get your own copy of 03-thread.py:

Download

Sample Code (.zip)

12.9 KB

To learn more, you can also check out the documentation.

00:00 All right. Let’s get back into a text editor and open up a file. I called mine 03-thread.py. We’re going to define myfunc(), and this time let’s give it a parameter of name. We’ll print, let’s use an f-string to print f'myfunc started with {name}', and then we’ll do time.sleep() again, 10 seconds.

00:29 And this time let’s print 'myfunc ended'. Then we’ll drop down into our entry point, if __name__ == '__main__': then we’re going to call myfunc() and we’re going to pass it a name of 'realpython'.

00:49 Just to help us learn, let’s actually do a print statement here in our main thread. We’ll say 'main started'.

00:58 And after we call it, let’s print 'main ended'. And we’re going to have to import time.

01:07 Then we can drop down and let’s execute our program.

01:14 We can see main started—our main thread has started. It called myfunc(), dropped into the myfunc() function, printed myfunc started with realpython, and then it hung there for a while where we didn’t have our shell prompt anymore while it was sleeping.

01:30 And then once it woke up, we see myfunc ended, and then since myfunc() returned back to our main thread, we went to line 11 here and we printed main ended.

01:44 Nothing special here. This just shows the blocking nature of how we couldn’t get to our last line here until myfunc() woke up and then printed myfunc ended, then we could finally finish our program.

02:02 Now let’s introduce threading into our program, so we’ll create our first thread. And before we do that, let’s just do our import. This is coming from the threading package.

02:13 Right above where we call myfunc() on line 11, I want you to go up and create a thread. I’m going to call mine t and we’ll say t = threading.Thread().

02:26 This is going to give us a Thread object, and we are going to have to pass it a couple of things. One is the target. This is the target function that we want our thread to execute.

02:37 And in this case, it’s myfunc, up here. The reason why I wanted to do a parameter and argument here is because I want to show that you can pass arguments to threads, and we can do that by using the args keyword argument, and we give it an iterable of our arguments.

02:58 We’ll keep it consistent and we’ll use the 'realpython' string. And now that we’ve created our thread and we’re going to use it to execute myfunc() with 'realpython' as the name, we don’t need to explicitly call myfunc() in our main thread.

03:22 We have now two threads—we have a multi-threaded application. We have our main thread that executes what’s in under if __name__ == '__main__':, and then we have t, which is going to execute myfunc() in a separate thread.

03:37 In order to actually use this thread or start the thread, we have an aptly-named function called .start(), and this just starts and kind of launches off this thread that’s going to execute myfunc(). So now that you know we have two threads and you know the nature of what multi-threaded applications can do, I want you to just maybe pause the video and think about what our print statements are going to look like.

04:06 We’ll drop back into our terminal and I’m going to clear the screen and execute our program again, and let’s see what happens.

04:16 All right, we see main started—this is our main thread printing that line. We created our thread, we started our thread. We told it to execute myfunc() with 'realpython' as the name.

04:30 And then that’s where we’re seeing myfunc started with realpython. This print statement here is actually coming from the t thread, not the main thread. The main thread started the t thread, and then it immediately went down and it went to the next line of execution, which is why we see main ended as the third line printed.

04:53 Notice how that was different. Before, main ended was the last line, because we had to wait for myfunc() to finish. So while t was in myfunc() sleeping, our main thread continued on and printed main ended.

05:09 When t woke up, that’s why we see the last line here of myfunc ended, printed by the t thread. This is the nature of threads. Once you start a thread, it’s going to go off and do its thing and then the rest of the program is going to continue executing.

05:27 Sometimes you want this to happen, sometimes you don’t, and throughout the course, we’re going to learn about more tools that give us control over how we use our threads.

hoangngocy on March 31, 2020

I have try this code on my computer but result was different, main thread had finished before the t thread.

devavrat on Nov. 20, 2020

From my understanding, Python is a synchronous, single threaded language and inherently does not support multi threading due to its global interpreter lock. Is this all true? If not, does Python truely support multi threading like Java? When you call the library “threading”, how does this work under the hood?

Bartosz Zaczyński RP Team on Nov. 20, 2020

@devavratk96 Python takes advantage of the OS-level threads just like Java, so in theory, it should be able to run code in parallel on multiple CPU cores. However, what’s preventing it from doing so is the infamous global interpreter lock (GIL), which is present in the standard CPython implementation. It was the most straightforward way to ensure thread-safety in Python and to integrate it with a plethora of C libraries. What this means in practice is that Python can only run one thread at any given time.

There have been attempts at removing GIL from Python, some actually successful, but they all made the single-threaded code run slower and broke the backward compatibility with the standard library. Other than that, there are a few alternative Python interpreters, such as IronPython or Jython, which execute on a completely different runtime like .Net of JVM. They both expose true threads but come with their own problems.

Another way to bypass the GIL is by offloading the processing to a C library. You could use an existing C library like NumPy, write your own Python extension module in C, or wrap a C function using ctypes. The list of tricks goes on and on. I haven’t talked about Cython, a hybrid of C and Python, or numba that enables the just-in-time (JIT) compilation of Python code.

Anyway, the canonical way to run Python code in parallel is to spread it over separate processes or even a cluster of geographically distributed computers. Naturally, that incurs additional costs, which might not always be justified.

So, do you need threads in Python at all?

Absolutely! First of all, threads let you simulate real-life processes more easily, even if they don’t truly run in parallel. If only you can decompose a problem into multiple concurrently running pieces, it might be easier to think about them. More importantly, however, threads can increase the throughput of a system where most of the work is done within I/O-bound tasks. A classic example would be a web server handling HTTP traffic. (You want to avoid using threads in Python for CPU-bound tasks, which can run as fast as the processing unit allows.)

ldr3tlogy on May 19, 2021

You guys’re awesome. :-)

Become a Member to join the conversation.