multiprocessing

11/29/2022

print view

Parallel Programming

There are several parallel programming models enabled by a variety of hardware (multicore, cloud computing, supercomputers, GPU).

Threads vs. Processes

A thread of execution is the smallest sequence of programmed instructions that can be managed independently by an operating system scheduler.

A process is an instance of a computer program.

Address Spaces

A process has its own address space. An address space is a mapping of virtual memory addresses to physical memory addresses managed by the operating system.

Address spaces prevent processes from crashing other applications or the operating system - they can only access their own memory.

Threads vs. Processes

What do we expect to print out?

The Answer

[224454]
[1397055]
[1397055]

Threads vs. Processes

The Answer

0
0

Threads/Process Objects

Thread and Process Objects have the same interface

  • start - start running the target function with (optional) args
  • join - wait for thread to finish before doing anything else
  • is_alive - returns true if thread is still running

Example

204433 True True
1417523 False False

Synchronization

When two threads update the same resource, their access to that resource needs to be gated.

There are a variety of synchronization primitives provided for both threads and processes (although they usually aren't needed for processes):

Lock

Can be acquired by exactly one thread (calling acquire twice from the same thread will hang). Must be released to be acquired by another thread. Basically, just wrap the critical section with an acquire-release pair.

RLock

A recursive lock. This is a lock that can be acquired multiple times by the same thread (and then must be released exactly the same number times). This is especially useful for modular programming. For example, you can acquire/release a lock around a function call without working about what that function is doing with the lock.

Lock Example

2000000

Communication

Threads communicate through their shared address space, and use locks to protect sensitive state. Several classes provide a simplified interface for communication (these are available with processes as well, but the underlying implementation is different).

Queue

Queue.Queue (multiprocess.Queue for processes) provides a simple, thread-safe, first-in-first-out queue.

  • put: put an element on the queue. This will block if the queue has filled up
  • get: get an element from the queue. This will block if the queue is empty.

Communication

Pipe

A pipe is a communication channel between processes that can send and receive messages. Unlike a queue, it is not okay for multiple threads to write to the same end of the pipe at the same time.

Pipe() return a tuple of Connection objects representing the ends of the pipe. Each connection object has the following methods:

  • send: sends data to other end of the pipe
  • recv: waits for data from other end of the pipe (unless pipe closed, then EOFError)
  • close: close the pipe

Communication

Since they don't have a shared address space, it is recommended the processes use exclusively either multiprocess.Queue or multiprocess.Pipe to communicate.

Use a pipe for communication between two processes (server-client architecture).

Use a queue for one-way communication between many threads (producer-consumer architecture).

Pipes

you sent me Hello!

Pools

multiprocess supports the concept of a pool of workers. You initialize with the number of processes you want to run in parallel (the default is the number of CPUs on your system) and they are available for doing parallel work:

  • map: the most important function - just like the built-in map, this will map a function to an iterable and return the result, but the mapping will be done in parallel. Blocks until the full result is computed and the result is in the proper order.
  • map_async: map without blocking
  • imap: parallel imap - returns iterable instead of list. The next() method of the returned iterable will block if necessary.
  • imap_unordered: same as imap but returns values in order they are computed
  • close: close the pool to prevent additional jobs from being submitted
  • join: must call close first; waits for all jobs to complete

Pool Example

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361]
0.6049268245697021
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361]
2.0040438175201416

Producer/Consumer

16

Bad Example

You should not produce and consume in the same thread. If outQ fills up, inQ will fill up and the put will block.

The empty/full methods of the Queue class are pretty much useless since their result depends on when they are called. Here, no values had been generated when it was called so nothing is printed.

In order communicate that there are no more values, you must send a sentinal value.

Threads or Processes?

Normally, the choice between threads and processes depends on how data will be accessed and the level of communication between parallel tasks etc.

However, in Python, the answer is almost always use multiprocessing, not threading.

Why? The CPython interpretter has a Global Interpretter Lock. This means that only one thread of python code is actually executed at any given time when using threads. With processes, each process has its own interpretter (with its own lock).

Embarassingly Parallel

Writing correct, high performance parallel code can be difficult, but some in some cases it's trivial.

A problem is embarassingly parallel if it can be easily separated into independent subtasks (i.e., no need to communicate between threads) each of which does a substantial amount of computation.

Fortunately this is often the case.

  • Apply this filter to 1000 images
  • Process these 5000 protein structures
  • Compute RMSDs of all frames in a trajectory

In cases like these, using Pools will get you a significant speedup (by however many cores you have).

Key Concepts

  • You can run many things at the same time with threads/processes
  • This is an easy way to make things faster, but can get complicated
  • Use multiprocess not threads
  • Processes can communicate with Queues/Pipes
  • 90% of the time all you need are Pools, and they are not complicated

Project

  • Take a search term as an argument
  • Use biopython to search the NCBI protein database for entries that match this term and are in the pdb
  • Extract the PDB entries
  • For each PDB entry, use ProDy to calculate the ANM modes (this can be done in parallel)
  • Sort the results based on the highest fractional variance in the first mode
  • Print out the top ten PDBs with the fractional variance of their first three modes
{'5RL6',
 '5RL7',
 '5RL8',
 '5RL9',
 '5RLB',
 '5RLC',
 '5RLD',
 '5RLE',
 '5RLF',
 '5RLG',
 '5RLH',
 '5RLI',
 '5RLJ',
 '5RLK',
 '5RLL',
 '5RLM',
 '5RLN',
 '5RLO',
 '5RLP',
 '5RLQ',
 '5RLR',
 '5RLS',
 '5RLT',
 '5RLU',
 '5RLV',
 '5RLW',
 '5RLY',
 '5RLZ',
 '5RM0',
 '5RM1',
 '5RM2',
 '5RM3',
 '5RM4',
 '5RM5',
 '5RM6',
 '5RM7',
 '5RM8',
 '5RM9',
 '5RMA',
 '5RMB',
 '5RMC',
 '5RMD',
 '5RME',
 '5RMF',
 '5RMG',
 '5RMH',
 '5RMI',
 '5RMJ',
 '5RMK',
 '5RML',
 '5RMM',
 '6LNN',
 '6LZ6',
 '6LZ8',
 '6M03',
 '6M39',
 '6W61',
 '6XDC',
 '6XHL',
 '6XHM',
 '6ZLW',
 '6ZMT',
 '6ZN5',
 '6ZON',
 '6ZP4',
 '7AGA',
 '7B3B',
 '7B3C',
 '7BZF',
 '7C2K',
 '7DDO',
 '7DDP',
 '7DGB',
 '7DGF',
 '7DGG',
 '7DGH',
 '7DGI',
 '7DHJ',
 '7DTE',
 '7DYD',
 '7E5X',
 '7E5Y',
 '7E8M',
 '7E9N',
 '7E9O',
 '7E9P',
 '7E9Q',
 '7EJY',
 '7EJZ',
 '7EK0',
 '7ENF',
 '7ENG',
 '7F2B',
 '7F2E',
 '7F3Q',
 '7F46',
 '7F6Y',
 '7F6Z',
 '7F7H',
 '7FH0',
 '7JYC',
 '7K3T',
 '7K40',
 '7K6D',
 '7K6E',
 '7KJR',
 '7KS9',
 '7LK9',
 '7LKA',
 '7MB4',
 '7MB5',
 '7MB6',
 '7MB7',
 '7MB8',
 '7MB9',
 '7MHF',
 '7MHG',
 '7MHH',
 '7MHI',
 '7MHJ',
 '7MHK',
 '7MHL',
 '7MHM',
 '7MNG',
 '7MRR',
 '7N5H',
 '7NFV',
 '7NIJ',
 '7OFS',
 '7OFT',
 '7OFU',
 '7Q5E',
 '7Q5F',
 '7QL8',
 '7QLF',
 '7QUB',
 '7QUW',
 '7R40',
 '7R4I',
 '7R4Q',
 '7R4R',
 '7RVM',
 '7RVN',
 '7RVO',
 '7RVP',
 '7RVQ',
 '7RVR',
 '7RVS',
 '7RVT',
 '7RVU',
 '7RVV',
 '7RVW',
 '7RVX',
 '7RVY',
 '7RVZ',
 '7RW0',
 '7RW1',
 '7S6W',
 '7S6X',
 '7S6Y',
 '7S6Z',
 '7S70',
 '7S71',
 '7S72',
 '7S73',
 '7S74',
 '7S75',
 '7T2T',
 '7T2U',
 '7T2V',
 '7T3Y',
 '7T3Z',
 '7T40',
 '7T41',
 '7T42',
 '7T43',
 '7T44',
 '7T45',
 '7T46',
 '7T48',
 '7T49',
 '7T4A',
 '7T4B',
 '7T70',
 '7T8M',
 '7T8R',
 '7T9Y',
 '7TA4',
 '7TA7',
 '7TB2',
 '7TBT',
 '7TC4',
 '7TE0',
 '7TLL',
 '7TUQ',
 '7TUU',
 '7TV0',
 '7U28',
 '7U29',
 '7UHB',
 '7UHC',
 '7V61',
 '7VAH',
 '7VIC',
 '7VLO',
 '7VLP',
 '7VLQ',
 '7VMU',
 '7VOA',
 '7VPY',
 '7VQ0',
 '7VTC',
 '7WHC',
 '7WO4',
 '7WO5',
 '7WO7',
 '7WOA',
 '7WOB',
 '7WOC',
 '7WOG',
 '7WPH',
 '7WPV',
 '7WWL',
 '7WWM',
 '7WZ1',
 '7WZ2',
 '7X7E',
 '7X7N',
 '7X7T',
 '7X7U',
 '7X7V',
 '7X9E',
 '7XAR',
 '7XIC',
 '7XID',
 '7XRP',
 '7XV7',
 '7XXL',
 '7Y42',
 '7Z0P',
 '7Z59',
 '7ZCE',
 '7ZCF',
 '7ZDQ',
 '7ZL1',
 '7ZR2',
 '8A4Q',
 '8A4T',
 '8ACD',
 '8ACL',
 '8DCC',
 '8DCE',
 '8DDI',
 '8DDM',
 '8DOY',
 '8DRR',
 '8DRS',
 '8DRT',
 '8DRU',
 '8DRV',
 '8DRW',
 '8DRX',
 '8DRY',
 '8DRZ',
 '8DS0',
 '8DS1',
 '8DS2',
 '8DTR',
 '8DTT',
 '8DTX'}