While building an algorithm in Python (interpreted language) that incorporated multi-threading I was surprised to see that execution time was not any better when compared to the sequential algorithm I had previously built. In an effort to understand the reason for this result I did some reading, and believe what I learned offers an interesting context from which to better understand the differences between multi-threading and multi-processes.
Multi-core systems may exercise multiple threads of execution, and so Python should support multi-threading. But Python is not a compiled language and instead is an interpreted language1. This means that the program must be interpreted in order to run, and the interpreter is not aware of the program before it begins execution. What it does know, however, are the rules of Python and it then dynamically applies those rules. Optimizations in Python must then be principally optimizations of the interpreter itself, and not the code that is to be run. This is in contrast to compiled languages such as C++, and has consequences for multi-threading in Python. Specifically, Python uses the Global Interpreter Lock to manage multi-threading.
On the other hand a compiled language is, well, compiled. The program is processed "entirely", where first it is interpreted according to its syntactical definitions, then mapped to a language agnostic intermediate representation, and finally linked into an executable code. This process allows the code to be highly optimized because it is all available at the time of compilation. The various program interactions and relationships are defined at the time the executable is created and robust decisions about optimization can be made.
In modern environments Python's interpreter must permit multi-threading, and this must both be safe and efficient. This is where the difference between being an interpreted language versus a compiled language enters the picture. The interpreter must not to disturb internally shared data from different threads, while at the same time optimizing the use of processors for computations.
As has been noted in the previous posts both a process and a thread are independent sequential executions with the primary difference being that memory is shared across multiple threads of a process, while processes isolate their memory spaces.
In Python data is protected from simultaneous access by different threads by the Global Interpreter Lock. It requires that in any Python program only one thread can be executed at any time. On the other hand it is possible to run multiple processes since the memory for each process is isolated from any other process, and processes can run on multiple cores.
1 Donald Knuth has a good explanation of interpretive routines in The Art of Computer Programming: Fundamental Algorithms.