Threads & Processes

Python's threading and multiprocessing modules can often serve the same purpose. However, the differences cause enough emergent profanity to warrant this post.

0. Why Thread?

The first time I really needed threads was GUI creation. With only one thread, the GUI will freeze "program is not responding"-ly for any time-consuming function. In my case, one function makes some HTTP requests that took about one second. One very noticeable second.

The solution was to use a QThread to do the requests in the background, send the data to the GUI thread when it's ready. One practical reason to thread then: When you want to do two things at once.

1. How's a Thread different to a Process?

# -*- coding: utf-8 -*-
# original name: thread_script.py
# author: dud1337
import sys
from threading import Thread
from time import sleep

class TestThread(Thread):
# Prints a message every n seconds
	def __init__(self, to_print, time_interval):
		Thread.__init__(self)
		self.s = to_print
		self.t = time_interval
		self.daemon = True	# kills thread on exit
	def run(self):
		while True:
			sleep(self.t)
			print self.s

threadA = TestThread('I\'m thread A!', 3)
threadB = TestThread('I\'m thread B.', 4)

print 'ctrl+C to exit'

threadA.start()
threadB.start()

# Preventing program from ending
try:
	while True:
		sleep(1)
except KeyboardInterrupt:
	print '\nSeeya!'
	sys.exit()

This script creates two threads, each printing to stdout over and over.

# -*- coding: utf-8 -*-
# original name: process_script
# author: dud1337
import sys
from multiprocessing import Process, current_process
from time import sleep

def test_function(interval):
# Prints a message every n seconds
	s = 'Hi, I\'m ' + current_process().name
	t = interval
	while True:
		sleep(t)
		print s
	
processA = Process(name='process A!', target=test_function, args=(3,))
processB = Process(name='process B.', target=test_function, args=(4,))

print 'ctrl+C to exit'

processA.start()
processB.start()

# To exit nicely
try:
	while True:
		sleep(1)
except KeyboardInterrupt:
	processA.terminate()
	processB.terminate()
	print '\nSeeya!'
	sys.exit()

This script creates two processes, each printing to stdout over and over. Their output looks the same:

i@pc:~$ python whichever_script.py
ctrl+C to exit
Hi, I'm process A!
Hi, I'm process B.
Hi, I'm process A!
^C
Seeya!

So what's the difference? Run both scripts and then the following

i@pc~$ ps -ef | grep -E "(process|thread)_script"
UID	PID	PPID	...	CMD	# this part won't be grep'd
you	238	138	...	python thread_script.py
you	338	139	...	python process_script.py
you	339	338	...	python process_script.py
you 	340	338	...	python process_script.py

So process_script.py is 3 processes, and thread_script.py is just 1. Why 3? The two processes + the "main" process running the sleepy while loop.

Notice the parent process id (PPID) for the last two is the second one. The two Processes in our code was created by the parent process, the main code. Now with -L

i@pc~$ ps -eLf | grep -E "(process|thread)_script"
UID	PID	PPID	LWP	...	CMD	# no grepperino
you	238	138	238	...	python thread_script.py
you	238	138	239	...	python thread_script.py
you	238	138	240	...	python thread_script.py
you	338	139	338	...	python process_script.py
you	339	338	339	...	python process_script.py
you 	340	338	340	...	python process_script.py

LWP (Light Weight Processes) are threads. As you can see they share the ID-space with regular processes, and their PID is still that of the primary python thread_script.py. Also contrast the PPID, the terminal that spawned the base process, unlike for processes.

Try running kill 239 and kill 339. The former will kill the whole threaded process, the later will just kill one of the subprocesses ("Hi, I'm process A!" will stop).

2. OK. So, how're they really different?

First off: PyInstaller with --onefile on windows does not like multiprocessing!

Source. This is a weird one, but a very annoying one. If you ever want to turn your python files into a single exe, try to use threading.

↑ Top  ⌂ Home