title: Multithreading in Python does not give the expected performance
body: <p>I am building an application to solve Matrices in Linear Algebra. I have this class:</p>
  <pre><code>class Matrix:
  def __init__(self):
      self.elements = []
      self.height = 0
      self.width = 0
  
  def __add__(self, matrix):
      resultant_matrix = []
      row = []
      for i in range(self.height):
          row.clear()
          for j in range(self.width):
              row.insert(j, self.elements[i][j] + matrix.elements[i][j])
          resultant_matrix.insert(i, row)
      return self.list_to_object(resultant_matrix)
  
  @staticmethod
  def list_to_object(list_matrix):
      matrix_obj = Matrix()
      matrix_obj.elements = list_matrix
      matrix_obj.height = len(list_matrix)
      matrix_obj.width = len(list_matrix[0])
      return matrix_obj
  
  @classmethod
  def get_matrix(cls, rows=3, columns=3):
      mat = Matrix()
      for i in range(rows):
          row = []
          for j in range(columns):
              row.append(j)
          mat.elements.append(row)
      mat.height = len(mat.elements)
      mat.width = len(mat.elements[0])
      return mat
  </code></pre>
  <p>My focus is on the <strong>add</strong> method that helps me to add to matrices.</p>
  <p>I have a method in the following class <code>addition</code> to split the matrices into smaller parts, then do the addition process on them, then reassemble them and return the new matrix.</p>
  <pre><code>class ThreadingManager:
  threads = []
  maximum_available_threads = 8
  
  def do_add(self, slice1, slice2, index, _list):
      m1 = Matrix.list_to_object(slice1)
      m2 = Matrix.list_to_object(slice2)
      elements = (m1 + m2).elements
      for i, row in enumerate(elements):
          _list[index+i] = row
  
  def addition(self, matrix_a, matrix_b):
      m1 = matrix_a.elements
      m2 = matrix_b.elements
  
      parts_number = self.maximum_available_threads
  
      sub_matrices_m2 = get_sub_matrix(m1, parts_number)
      sub_matrices_m3 = get_sub_matrix(m2, parts_number)
      new_list = [[] * matrix_a.width] * matrix_a.height
  
      for i in range(len(sub_matrices_m2)):
          sub_matrices_m2_i = sub_matrices_m2[i]
          sub_matrices_m3_i = sub_matrices_m3[i]
          rows_number_per_thread = i*len(sub_matrices_m2_i)
          thread = threading.Thread(target=self.do_add, args=(sub_matrices_m2_i, sub_matrices_m3_i,
                                                              rows_number_per_thread, new_list))
          thread.start()
          self.threads.append(thread)
  
      for thread in self.threads:
          thread.join()
          self.threads.remove(thread)
      return Matrix.list_to_object(new_list)
  </code></pre>
  <p>Finally, I have this test case (You can change the <code>50000</code> to any number, depending on your computer resources)</p>
  <pre><code>mat_a = Matrix.get_matrix(50000, 5)
  mat_b = Matrix.get_matrix(50000, 5)
  
  start = datetime.now()
  mat_c = mat_a + mat_b
  end = datetime.now()
  print('time needed before multithreading is:', end - start)
  
  start = datetime.now()
  result = ThreadingManager().addition(mat_a, mat_b)
  end = datetime.now()
  print('time needed before multithreading is:', end - start)
  
  </code></pre>
  <p>My problem is that I cannot find any difference between the normal addition and multithreading. How can I use multithreading in a better way?</p>
  <p><strong>Note:</strong> If I removed the line <code>row.clear()</code> in the <code>Matrix</code> class, I have a wrong answer but I can notice the differences after multithreading</p>
  <p>needed imports</p>
  <pre><code>import math
  import threading
  from datetime import datetime
  </code></pre>