What is Python Global Interpreter Lock (GIL)

What problem the GIL for Python solved:

Python has something that other languages ​​don`t have, it`s reference counting. With a reference count, we can count the total number of references made inside Python in order to assign a value to a data object. Thanks to this counter, we can count references, and when this number reaches zero, the variable or data object will be automatically freed. For example

# Show Python program
# use link count

import sys

 

geek_var = "Geek"

print (sys.getrefcount (geek_var))

  

string_gfg = geek_var

print (sys.getrefcount (string_gfg))

Exit :

 4 5 

This reference count variable needed to be protected because sometimes two threads were taken away read or decrement their value at the same time, doing this, which can lead to a memory leak, so to protect the thread, we add locks to all data structures that are shared by threads, but sometimes by adding Locks, there are multiple locks that lead to another problem, which is a dead end. To avoid problems with memory leaks and deadlocks, we used a single interpreter lock, which is the global interpreter lock (GIL).

Why the GIL was chosen as the solution:
Python supports the C language in the backend and all related libraries are mostly written in C and C ++. Thanks to the GIL, Python provides the best way to deal with thread-safe memory management. The global interpreter lock is easy to implement in python as it only requires one lock per thread to be processed in python. The GIL is simple to implement and was easily added to Python. This provides improved performance for single-threaded programs because only one lock needs to be managed.

Impact on multithreaded Python programs:
When a user writes Python programs or any computer programs, there is a difference between those related to the processor in their performance and those related to I / O. The processor pushes the program to its limit by performing many operations at the same time, while the I / O program had to spend time waiting for I / O. For example
Code 1: A processor-related program that does a simple countdown

# Show Python program
# Processor related software

 

import time

from threading import Thread

  

COUNT = 50000000

 

def countdown ( n):

while n & gt;  0 :

n - = 1

 

start = time.time ()

countdown (COUNT)

end = time.time ()

 

print ( `Time taken in seconds -` , end - start)

Exit:

 Time taken in seconds - 2.5236213207244873 

Code 2: two streams run in parallel

< / p>

# Display Python program
# two streams run in parallel

 

import time

from threading import Thread

 

COUNT = 50000000

 

def countdown (n):

  while n & gt; 0 :

n - = 1

 

t1 = Thread (target = countdown, args = (COUNT / / 2 ,))

t2 = Thread (target = countdown, args = (COUNT / / 2 ,))

 

start = < / code> time.time ()

t1.start ()
t2.start ()
t1.join ()
t2. join ()

end = time. time ()

 

print ( `Time taken in seconds -` , end - start)

Exit :

 Time taken in seconds - 2.183610439300537 

As you can see there are two codes in the above code where the process is bound cpu and multithreaded process have the same performance as in a CPU bound program, since the GIL limits the cpu to work with only one thread. The impact of cpu thread and multithreading in Python will be the same.

Why the GIL hasn`t been removed yet:

The GIL is not improving at this time because that Python 2 has a GIL implementation and if we change that in Python 3 it will create a problem for us. So instead of removing the GIL, we are improving the concept of the GIL. This is one reason not to remove the GIL, as python relies heavily on C in the backend and C extension relies heavily on the GIL implementation methods. While there are many other ways the GIL solves problems, most of them are difficult to implement and can slow down the system.

How to deal with the Python GIL:

We use multiprocessing most of the time to prevent the GIL. In this implementation, python provides a different interpreter for each process that starts, so in this case a separate thread is provided for each process in multiprocessing.

# Display Python program
# multiprocessing

 

import multiprocessing 

import time

 

COUNT = 50000000

  

def countdown (n):

while n & gt; 0 :

  n - = 1

 

if __ name__ = = "__ main__" :

# creating processes

start = time.time ()

p1 = multiprocessing.Process (target = countdown , args (COUNT / / 2 ,))

  p2 = multiprocessing.Process (target = countdown, args = (COUNT / / 2 ,))

 

# start process 1

p1.start ()

# start process 2

  p2.start () 

  

# wait for process 1 to finish

p1.join () 

# wait for process 2 to finish

  p2.join ()

end = time.time ()

print ( `Time taken in seconds -` , end - start)

Exit :

 Time taken in seconds - 2.5148496627807617 

As you can see, there is no difference between time consumed by a multi-threaded system and a multiprocessing system. This is due to the fact that a multiprocessor system has to solve its own problems. So it doesn`t fix the problem, but yes, it does provide a solution that the GIL allows you to do with python.