In previous post (here) I described some approach how I overcame some limitation of Matplotlib when plotting large data sets. I also mentioned that using both cores of processor could further speed up calculation. Now we’ll see some short example how to use multicore and threading. According the python’s documentation, multiprocessing APIs was designed to mimic those of threading. In the next we will see simple examples and some comparison.
Here is some example that uses threading class to add some random number to the list. We create two threads (thread ‘A’ and ‘B’) and run them. Each thread let us know, when finished.
#!/usr/bin/python import random import threading times = 10000000 def process(count, jobid, output): pom =  for i in range(count): pom.append(random.random()) print "Job ", jobid, " finished!" out1 = list() out2 = list() thread1 = threading.Thread(target=process(times, 'A', out1)) thread2 = threading.Thread(target=process(times, 'B', out2)) job =  job.append(thread1) job.append(thread2) for i in job: i.start() for i in job: i.join() print "Finished!"
We created two threads (thread1 and thread2) and assign them target to do. then we run them and wait for result. This time just to write that they are finished. Output on dual-core processor can look like this;
box$ time python test-thread.py Job A finished! Job B finished! Finished! real 0m8.175s user 0m7.558s sys 0m0.595
Here it took over 8 s to feel the list with 10 000 000 random numbers (twice). Using multiprocessing, we can rewrite the code something like this;
#!/usr/bin/python import multiprocessing as mp from multiprocessing import Process import random times = 10000000 def process(count, jobid, output): pom =  for i in range(count): pom.append(random.random()) print "Job ", jobid, " finished!" out1 = list() out2 = list() job =  job.append(Process(target=process, args=(times, 'A', out1))) job.append(Process(target=process, args=(times, 'B', out2))) for j in job: j.start() for j in job: j.join() print "Finished!"
Syntax is pretty similar. We run this code on dual-core processor and output can look like this;
box$ time python test-multiproc.py Job B finished! Job A finished! Finished! real 0m4.834s user 0m7.619s sys 0m1.001s
Process ‘B’ finished faster then process ‘A’. It is because they run concurrently, whereas in threading case we run threads one after the other. We can see, that consumed user time is about the same; however, the real time it took us to co this job was shorter (8.175s vs. 4.834s). The reason is, that Python have something called Python Global Interpreter Lock (GIL). Even we create multiple threads, they will run just on one core. In case of multiprocessing the load is distributed on more cores.
Time of the multiprocessing case is not exactly half, since there is also some time when we are running just one thread (app itself) and management takes probably also some time. In case of long time running multiprocessing code there can be proximately 50% time save when used two cores except one. So with X cores (X > 1) code should run proximately X-times faster.
I used similar multiprocessing code to calculate rays in ray tracing. Each core got “several” rays to calculate and if there was split, then the child ray was added at the end of the feed in list. To synchronize the read and write to the same list, it is probably better to use Manager, that will take care of that.
from multiprocessing import Process, Manager ... manager = Manager() out1 = manager.list() out2 = manager.list() ...
This way we have managed list, which we can read from and write to safely.
Threading is good enough in many applications. E.g. when we need something to measure and we want application (app window) to be responsive during measurement (measurement can be one thread and app run as other thread). If we need to run the code concurrently, then there is multiprocessing. More information can be found on the Python’s documentation pages or in forums, e.g. http://stackoverflow.com/questions/990102/python-global-interpreter-lock-gil-workaround-on-multi-core-systems-using-tasks.