2

I have the following task that I would like to make faster via multi threading (python3).

import threading, time

q = []

def fill_list():
    global q
    while True:
        q.append(1)
        if len(q) >= 1000000000:
            return

The first main does not utilize multithreading:

t1 = time.clock()
fill_list()
tend = time.clock() - t1
print(tend)

And results in 145 seconds of run time.

The second invokes two threads:

t1 = time.clock()
thread1 = threading.Thread(target=fill_list, args=())
thread2 = threading.Thread(target=fill_list, args=())

thread1.start()
thread2.start()

thread1.join()
thread2.join()

tend = time.clock() - t1
print(tend)

This takes 152 seconds to complete.

Finally, I added a third thread.

t1 = time.clock()
thread1 = threading.Thread(target=fill_list, args=())
thread2 = threading.Thread(target=fill_list, args=())
thread3 = threading.Thread(target=fill_list, args=())

thread1.start()
thread2.start()
thread3.start()

thread1.join()
thread2.join()
thread3.join()

tend = time.clock() - t1
print(tend)

And this took 233 seconds to complete.

Obviously the more threads I add, the longer the process takes, though I am not sure why. Is this a fundamental misunderstanding of multithreading, or is there a bug in my code that is simply repeating the task multiple times instead of contributing to the same task?

Malonge
  • 1,980
  • 5
  • 23
  • 33

1 Answers1

5

Answers 1 and 2.

First of all, your task is CPU-bound, and in a Python process only one thread may be running CPU-bound Python code at any given time (this is due to the Global Interpreter Lock: https://wiki.python.org/moin/GlobalInterpreterLock ). Since it costs quite a bit of CPU to switch threads (and the more threads you have, the more often you have to pay that cost), your program doesn't speed up: it slows down.

Second, no matter what language you're using, you're modifying one object (a list) from multiple threads. But to guarantee that this does not corrupt the object, access must be synchronized. In other words, only one thread may be modifying it at any given time. Python does it automatically (thanks in part to the aforementioned GIL), but in another lower-level language like C++ you'd have to use a lock or risk memory corruption.

The optimal way to parallelize tasks across threads is to ensure that the threads are as isolated as possible. If they access shared objects, those should be read-only, and cross-thread writes should happen as infrequently as possible, through thread-aware data structures such as message queues.

(which is why the most performant parallel systems like Erlang and Clojure place such a high emphasis on immutable data structures and message-passing)

Max Noel
  • 8,810
  • 1
  • 27
  • 35