Monday 10 December 2018

Threading Things Together


I would predict with a high degree of certainty that all software engineers reading this could relay war stories of issues caused by multi-threading.

Cast very much under the guise of a necessary evil, multi-threading seems to have a unique ability to cause confusion with difficult to diagnose defects and perplexing behaviour. Because of this features of many popular languages are evolving to try and help developers deal with this complexity and find better routes around it.

So if we can't live without threads how can we learn to live with them?

Recognising Dragons

One of the best ways to deal with potential issues is to recognise them coming over the horizon and try and steer the code base away from them.

Chief among these potential issues are race conditions, events happening out of the expected order and therefore driving unexpected outcomes. These issues are nearly always caused by the mutation of data by multiple threads. Whenever you decide to transition an area of code to be multi-threaded particular attention needs to be paid to the reading and writing of data, left unchecked these areas will almost certainly cause you problems.

Once your system goes multi-threaded you will very often end up in a situation where there is some dependency between them, this situation can all to easily end up with a deadlock, Thread A requires Thread B to complete its action but Thread B cannot complete because it requires resources currently being held by Thread A, nobody can move forward and the system is deadlocked.

It's also important to realise that threads are not entirely an abstract construct, they do have an underlying reliance on infrastructure and are therefore finite. It is therefore possible for your system to become starved of threads and hit a limitation in throughput.

Learning the Language

Multi-threading is definitely an area of software engineering where going your own way is not advisable. The complexity of implementing an effective implementation combined with the potential pitfalls aren't conducive to devising custom solutions.

Let others take the strain by learning the conventions for multi-threading within your chosen language, these are likely the result of a large number of man hours by experts and will be demonstrably effective and robust.

It's likely that various approaches are possible operating with varying degrees of complexity. As with many areas of software development a healthy aversion to complexity will not steer you far wrong, always strive for the simplest possible solution.

Many languages are developing features to remove the concern around thread creation and management from the shoulders of developers, a prime example of this would be the introduction of async\await within .NET or Completablefuture in Java. Whilst it will still be possible to cause yourself problems with these kinds of features hopefully it's harder to blow your foot off.

Strategic Decisions

Even once you understand the constructs of your language it's still imperative that your codebase has a defined strategy around multi-threading.

Trying to analyse the behaviour of a code base where new threads maybe put into operation at any moment is infinitely harder than assessing code that will transition to be multi-threaded in a predictable and prescribed manner.

If this strategy is well thought through then it can look to protect developers from some of the common pitfalls.

Any sufficiently complex software will require the use of multi-threading to deal with the throughput demands being placed on it. A healthy dose of fear around this aspect of code usually comes to all developers as their experience grows.

Use this fear as a catalyst for defining a strategy and a healthy laziness in looking for language features to take the strain. Multi-threading may on occasion cause you to have sleepiness nights but it is possible to find a way to let the magic happen without disaster being round corner.

No comments:

Post a Comment