The Nature of Threading: A Deep Dive into Multithreaded Execution
One of the fundamental concepts in computer science is threading, which allows multiple tasks to run concurrently on a single processing unit. However, due to the limitations of modern computing hardware, it's impossible to truly execute two threads simultaneously at the exact same time. This is because the operating system and CPU scheduler have to manage resources and prioritize threads based on various factors, resulting in an interleaved execution model.
To demonstrate this concept, let's create two threads that run a countdown function from 5 to 0. We'll start both threads simultaneously and observe how they execute. As we can see, the threads are trying to do the same thing at the same time, but due to the limitations of threading, they cannot truly run in parallel.
The key insight here is that when one thread is not executing a particular instruction, the other thread can take over and execute that instruction. However, this means that the threads are actually running in an interleaved manner, with each thread switching between different instructions based on the CPU scheduler's decisions. This leads to a situation where one thread may run multiple times before the other thread has a chance to finish.
To illustrate this point, let's take a closer look at the code and its execution sequence. We'll use a console output to display the progress of each thread as it runs. As we can see, Thread 1 runs for two consecutive iterations before switching back to Thread 2, which means that Thread 1 has finished executing the first iteration of the countdown function.
Now, let's introduce a time.sleep() delay in the code to simulate some work being done by each thread. We'll set Thread 1 to wait for 1 second and Thread 2 to wait for 1.5 seconds. When we run both threads simultaneously, we can observe how they execute in an interleaved manner.
Thread 1 runs for 1 second, then switches back to Thread 2, which is still waiting for its 1.5-second delay to complete. Once the delay has finished, Thread 2 executes a few instructions before switching back to Thread 1 again. This process continues until both threads have completed their respective tasks.
The interleaved execution model of threading has implications for how we design and implement multithreaded programs. It means that we need to carefully manage shared resources and synchronization mechanisms to avoid conflicts between threads. In the next video, we'll explore some real-world examples of threading and networking in more depth, which will help us understand the nuances of this topic better.
In conclusion, the nature of threading is complex and fascinating, with many intricacies that can be challenging to grasp at first. However, by understanding how threads execute and interleave with each other, we can develop a deeper appreciation for the challenges and opportunities presented by multithreaded programming.
"WEBVTTKind: captionsLanguage: enhey guys and welcome back to a new tutorial series on multi-threading in Python now before I go too far I just want to quickly mention that if you guys need any help throughout these videos all this stuff and all the code will be up on my website Tech with Tim done net and you guys can feel free to join my discord server where I have a bunch of people that are always willing to help out and talk about programming and coding the links in the description for that and also you guys should follow my Twitter for some exclusive updates and when the next videos are coming out I'm going with that if you guys can like the content and you like what I'm doing on this channel consider supporting me for a small donation per month by becoming a patreon or by providing a donation via PayPal I don't make very much money off of youtube and any donation really is greatly appreciated okay so with that being said let's get right into this video and talk about what is threading so you've probably heard about threading before and it probably heard about it talks about the same kind of sentence as processes and a lot of people get confused on what the difference between a process and a threat is so that's what I'm gonna explain here and we're gonna show a bit of an example code near the end of the video illustrating kind of the differences so here I'm in my task manager and you can see we have a long list of processes and you guys have probably done this before open up task manager and maybe ended a task or ended a process and whatnot okay so these are all our processes and essentially what a process is is a program running on your computer so now let's go to the performance tab and let's go to our CPU and let's have a look down here at the bottom you can see we have a hundred fifty two processes running and 1,700 threats so what is a threat well essentially each process is made up of different threads could be one thread but oftentimes it's many different threads and when a thread is essentially a task associated with a process okay so now we're gonna move over to this little illustration where I'm going to kind of try to show this to you so I have kind of a bunch of different things on the screen here we're gonna link these up in how they work together so we have this Python file which is what we write as the programmer okay we have some threads we have some processes we have our computer's memory and a CPU and just take note this a 4-course CPU so let's start with processes we already know to processes essentially it's a program that's running on our computer now what happens when we create a new process essentially is in in memory so in RAM we allocate some space for our process we say well creating this new process so everything that has to do with this process we're gonna store in box one and bucks one is just a little slice of RAM and that slice can be extended or it can be can become smaller but we have like a general location for that process and that's where it stores all of its variables all the information that it needs okay and that happens for all of our processes so whenever we create a new process we allocate a little slice of RAM for those processes now when we do that Ram is kind of like a queue and essentially it's gonna indicate what needs to run first what needs to run second and what needs to happen on a single core processor what would happen is will we read information from RAM like we read an instruction and then we'd write something back into RAM and we just keep doing that and keep following kind of the sequence of things that have been added to RAM but on a four core processor instead of doing things one at a time what we can do is we can actually split up all of the different tasks and all the different processes that we have into well four groups essentially allowing us to do things four times faster not quite but some one okay so we say well if we have four processes let's split these up onto each core of our CPU because each core can perform its own operations and do its own things independent of the other so we'll say well process one which is going to be stored in RAM right can go to court one process two can go to core two three two three and four to four you guys get the point right and that's why I labeled them here allowing us essentially to do four things at once in parallel so at the exact same time we can be doing four things now on each core let's say though we can't be doing something at the same time on a core so let's say we need to add a number and then we need to subtract a number and that's like core ones job is to add something then subtract something well we first have to add and then we have to subtract we can't do it we can't do it that at the exact same time right we got to perform operation one and then we can do operation two now if we had that split onto two cores well we could technically do those at the same time we can add one and subtract one at the exact same time because they're on two separate cores this is kind of an important concept to understand so now we kind of understand how processes work with the CPU they go into RAM they have their own little space and then their kind of instructions the work that needs to be done gets divvied up between the cores on our CPU essentially allowing us for it to do four things at once in parallel at the same time so now let's talk about threads where do these come in well each process is made up of a bunch of different threads and these threads are essentially different tasks that are running now one thread only one thread can be running at a time we can't have two threads running at the exact same time like they can be stacked up like we can have two threads that we've created but they're come their commands and their execution like if we need to add something and subtract things they have to happen one after another they can't happen in parallel they're happening in sequence okay so that's where threads come in they essentially are just a bunch of tasks that make up a process so where does this come in with our Python file well up until now likely you've probably just been creating single threaded programs which mean that we have to wait for one line of code to execute before the next line of code executes and that's just the way that it works right we can't have two things happening at the same time now when we create threads that is still true except we can actually switch between threads so we can say like maybe we have a function running we have two functions running we have one function that maybes counting to 100 another function that's counting to 50 well let's say maybe the function that's counting to 50 needs to stop for a second and wait for maybe some user input or something to happen well rather than not allowing the other function to run what we'll do is we'll simply say okay well if this one's waiting let's switch to the other thread and let that run so I'm going to show you an example now with some code and hopefully this will make this a bit more clear but essentially just I hope we kind of understood how this flows a little bit all right so if you get it just a little bit then you guys should be good for this part okay so now I'm just gonna run you through some code that I wrote this actually from the Python website I just kind of copied and modified it a bit that explains our kind of shows creating two threads and then their execution and how they kind of switch between one another so remember I was saying right that if one thread is waiting for something else to happen like it's not executing any commands it's just sitting there it's idle it's waiting then rather than waiting for that thing to happen let's run something else in the background right we have two threads running and one is waiting well the other thread can start running if that thread starts waiting and the other one has some commands and needs to be executed let's do that because we can never truly do things in threads at the exact same time we can't do two things like in the exact same like nanosecond millisecond right they have to happen one after each other but if one thread is not doing something the other thread can be doing something so that's how we kind of switch between things so let me just run this and then we'll really dissect what's happening so let's make this fullscreen and you can see that we go thread one thread two one two one two inch wait for this to happen sweet so essentially what we've done here is thread one runs okay and it's what what these both these threads are trying to do by the way is countdown to zero so starting at five they're trying to go to zero once they get to zero there takes it so thread we start thread one and then we start thread two and then thread one runs it goes five thread two runs it goes five okay and then thread one runs thread two runs thread one runs and would you look at this thread one runs again because it's not waiting it just keeps going so now thread one's waiting for a second so thread two goes thread two's waiting for a second thread one goes thread once done and now thread two goes until it finishes it's kind of a weird execution but we'll show why this happens if I just go here so essentially what I'm doing is I'm creating two threads and each thread runs this function it's called print time and all it does is it takes a counter which is going to start at five and it tries to get down to zero but notice that I have this time dot sleep in here and this sleep is essentially delaying the function a certain amount of time so my thread one every time it runs delays one second and my thread two every time it runs delays one point five seconds so what happens is and will bring up this console again is one thread one runs it does this it says Walt counter which means we're not at zero essentially time don't sleep delay the delay is one second so we can see we wait one second and then we run thread one and thread one goes five it prints that out it prints the time and then you can even see here it delays one second so it comes back up this while loop delays one second and since it's delayed we say okay well this is a perfect opportunity to run thread to so thread two gets run and you can see that it happens exactly one second after thread one runs because well it was waiting right and once that time sorry so it went okay now thread two delays for that little second of time and while thread two is delaying well it says okay this is a perfect opportunity to run thread one so then thread one runs and then what happens is thread one delays again right it does this little delay and then says oh well we're delaying thread one nothing's running so let's go to thread to sew it swaps goes to thread two runs same thing thread three runs because we're delaying thread to you for a second right and then we go back to thread one thread two thread one and so on now you might notice here that thread one runs twice in a row now that's because well thread one runs all right and see it's only delaying one second whereas thread two is delaying one point five seconds so this is kind of a weird but just think about this example right so thread two goes it delays one-and-a-half seconds this this still a happens one and a half seconds thread one runs it delays one second now the thing is when we go back to thread - it's still delaying like it's still in it's still waiting for one-and-a-half seconds to go by so if that's happening and we switch back to thread one thread one well maybe it's done is delay it's done it's one second delay so it just executes again and then it delays for one second so we switch back to thread two we check that delay that he's done so we run thread two and then we just keep going through the program and notice that it says exit' thread 1 I'm just telling us that we're finished we've done counting to 5 so we're gonna get out of that thread and then thread 2 we'll just go one after each other until it eventually exits and is done and that is kind of how threading works I know this might have been a little bit confusing in the next videos I think things will really clear up when we do some real-world examples of threading and maybe a bit of networking stuff which is a good example of threading yeah with that being said I hope you guys enjoyed the video if you do please make sure you leave a like and subscribe to the channel and let me know if there's anything that you'd like me to improve on in terms of explanation or to explain a little bit better in the next videohey guys and welcome back to a new tutorial series on multi-threading in Python now before I go too far I just want to quickly mention that if you guys need any help throughout these videos all this stuff and all the code will be up on my website Tech with Tim done net and you guys can feel free to join my discord server where I have a bunch of people that are always willing to help out and talk about programming and coding the links in the description for that and also you guys should follow my Twitter for some exclusive updates and when the next videos are coming out I'm going with that if you guys can like the content and you like what I'm doing on this channel consider supporting me for a small donation per month by becoming a patreon or by providing a donation via PayPal I don't make very much money off of youtube and any donation really is greatly appreciated okay so with that being said let's get right into this video and talk about what is threading so you've probably heard about threading before and it probably heard about it talks about the same kind of sentence as processes and a lot of people get confused on what the difference between a process and a threat is so that's what I'm gonna explain here and we're gonna show a bit of an example code near the end of the video illustrating kind of the differences so here I'm in my task manager and you can see we have a long list of processes and you guys have probably done this before open up task manager and maybe ended a task or ended a process and whatnot okay so these are all our processes and essentially what a process is is a program running on your computer so now let's go to the performance tab and let's go to our CPU and let's have a look down here at the bottom you can see we have a hundred fifty two processes running and 1,700 threats so what is a threat well essentially each process is made up of different threads could be one thread but oftentimes it's many different threads and when a thread is essentially a task associated with a process okay so now we're gonna move over to this little illustration where I'm going to kind of try to show this to you so I have kind of a bunch of different things on the screen here we're gonna link these up in how they work together so we have this Python file which is what we write as the programmer okay we have some threads we have some processes we have our computer's memory and a CPU and just take note this a 4-course CPU so let's start with processes we already know to processes essentially it's a program that's running on our computer now what happens when we create a new process essentially is in in memory so in RAM we allocate some space for our process we say well creating this new process so everything that has to do with this process we're gonna store in box one and bucks one is just a little slice of RAM and that slice can be extended or it can be can become smaller but we have like a general location for that process and that's where it stores all of its variables all the information that it needs okay and that happens for all of our processes so whenever we create a new process we allocate a little slice of RAM for those processes now when we do that Ram is kind of like a queue and essentially it's gonna indicate what needs to run first what needs to run second and what needs to happen on a single core processor what would happen is will we read information from RAM like we read an instruction and then we'd write something back into RAM and we just keep doing that and keep following kind of the sequence of things that have been added to RAM but on a four core processor instead of doing things one at a time what we can do is we can actually split up all of the different tasks and all the different processes that we have into well four groups essentially allowing us to do things four times faster not quite but some one okay so we say well if we have four processes let's split these up onto each core of our CPU because each core can perform its own operations and do its own things independent of the other so we'll say well process one which is going to be stored in RAM right can go to court one process two can go to core two three two three and four to four you guys get the point right and that's why I labeled them here allowing us essentially to do four things at once in parallel so at the exact same time we can be doing four things now on each core let's say though we can't be doing something at the same time on a core so let's say we need to add a number and then we need to subtract a number and that's like core ones job is to add something then subtract something well we first have to add and then we have to subtract we can't do it we can't do it that at the exact same time right we got to perform operation one and then we can do operation two now if we had that split onto two cores well we could technically do those at the same time we can add one and subtract one at the exact same time because they're on two separate cores this is kind of an important concept to understand so now we kind of understand how processes work with the CPU they go into RAM they have their own little space and then their kind of instructions the work that needs to be done gets divvied up between the cores on our CPU essentially allowing us for it to do four things at once in parallel at the same time so now let's talk about threads where do these come in well each process is made up of a bunch of different threads and these threads are essentially different tasks that are running now one thread only one thread can be running at a time we can't have two threads running at the exact same time like they can be stacked up like we can have two threads that we've created but they're come their commands and their execution like if we need to add something and subtract things they have to happen one after another they can't happen in parallel they're happening in sequence okay so that's where threads come in they essentially are just a bunch of tasks that make up a process so where does this come in with our Python file well up until now likely you've probably just been creating single threaded programs which mean that we have to wait for one line of code to execute before the next line of code executes and that's just the way that it works right we can't have two things happening at the same time now when we create threads that is still true except we can actually switch between threads so we can say like maybe we have a function running we have two functions running we have one function that maybes counting to 100 another function that's counting to 50 well let's say maybe the function that's counting to 50 needs to stop for a second and wait for maybe some user input or something to happen well rather than not allowing the other function to run what we'll do is we'll simply say okay well if this one's waiting let's switch to the other thread and let that run so I'm going to show you an example now with some code and hopefully this will make this a bit more clear but essentially just I hope we kind of understood how this flows a little bit all right so if you get it just a little bit then you guys should be good for this part okay so now I'm just gonna run you through some code that I wrote this actually from the Python website I just kind of copied and modified it a bit that explains our kind of shows creating two threads and then their execution and how they kind of switch between one another so remember I was saying right that if one thread is waiting for something else to happen like it's not executing any commands it's just sitting there it's idle it's waiting then rather than waiting for that thing to happen let's run something else in the background right we have two threads running and one is waiting well the other thread can start running if that thread starts waiting and the other one has some commands and needs to be executed let's do that because we can never truly do things in threads at the exact same time we can't do two things like in the exact same like nanosecond millisecond right they have to happen one after each other but if one thread is not doing something the other thread can be doing something so that's how we kind of switch between things so let me just run this and then we'll really dissect what's happening so let's make this fullscreen and you can see that we go thread one thread two one two one two inch wait for this to happen sweet so essentially what we've done here is thread one runs okay and it's what what these both these threads are trying to do by the way is countdown to zero so starting at five they're trying to go to zero once they get to zero there takes it so thread we start thread one and then we start thread two and then thread one runs it goes five thread two runs it goes five okay and then thread one runs thread two runs thread one runs and would you look at this thread one runs again because it's not waiting it just keeps going so now thread one's waiting for a second so thread two goes thread two's waiting for a second thread one goes thread once done and now thread two goes until it finishes it's kind of a weird execution but we'll show why this happens if I just go here so essentially what I'm doing is I'm creating two threads and each thread runs this function it's called print time and all it does is it takes a counter which is going to start at five and it tries to get down to zero but notice that I have this time dot sleep in here and this sleep is essentially delaying the function a certain amount of time so my thread one every time it runs delays one second and my thread two every time it runs delays one point five seconds so what happens is and will bring up this console again is one thread one runs it does this it says Walt counter which means we're not at zero essentially time don't sleep delay the delay is one second so we can see we wait one second and then we run thread one and thread one goes five it prints that out it prints the time and then you can even see here it delays one second so it comes back up this while loop delays one second and since it's delayed we say okay well this is a perfect opportunity to run thread to so thread two gets run and you can see that it happens exactly one second after thread one runs because well it was waiting right and once that time sorry so it went okay now thread two delays for that little second of time and while thread two is delaying well it says okay this is a perfect opportunity to run thread one so then thread one runs and then what happens is thread one delays again right it does this little delay and then says oh well we're delaying thread one nothing's running so let's go to thread to sew it swaps goes to thread two runs same thing thread three runs because we're delaying thread to you for a second right and then we go back to thread one thread two thread one and so on now you might notice here that thread one runs twice in a row now that's because well thread one runs all right and see it's only delaying one second whereas thread two is delaying one point five seconds so this is kind of a weird but just think about this example right so thread two goes it delays one-and-a-half seconds this this still a happens one and a half seconds thread one runs it delays one second now the thing is when we go back to thread - it's still delaying like it's still in it's still waiting for one-and-a-half seconds to go by so if that's happening and we switch back to thread one thread one well maybe it's done is delay it's done it's one second delay so it just executes again and then it delays for one second so we switch back to thread two we check that delay that he's done so we run thread two and then we just keep going through the program and notice that it says exit' thread 1 I'm just telling us that we're finished we've done counting to 5 so we're gonna get out of that thread and then thread 2 we'll just go one after each other until it eventually exits and is done and that is kind of how threading works I know this might have been a little bit confusing in the next videos I think things will really clear up when we do some real-world examples of threading and maybe a bit of networking stuff which is a good example of threading yeah with that being said I hope you guys enjoyed the video if you do please make sure you leave a like and subscribe to the channel and let me know if there's anything that you'd like me to improve on in terms of explanation or to explain a little bit better in the next video\n"