Let's start looking at a more asynchronous way of implementing concurrency. Futures wrap either multiprocessing or threading depending on what kind of concurrency we need (tending toward I/O versus tending toward CPU). They don't completely solve the problem of accidentally altering shared state, but they allow us to structure our code such that it is easier to track down when we do so.
Futures provide distinct boundaries between the different threads or processes. Similar to the multiprocessing pool, they are useful for call and answer type interactions, in which processing can happen in another thread and then at some point in the future (they are aptly named, after all), you can ask it for the result. It's really just a wrapper around multiprocessing pools and thread pools, but it provides a cleaner API and encourages nicer code.
A future is an object...