Monday, November 15, 2004

Threads increase design time

Keeping with my regular "post every couple of months" trend here is the next installment in my discussion of threads.

In addition to pulling from my personal experience I've been doing research on threading (which is half of the reason I write this blog). I have been amazed at the chorus of other voices warning of threading problems. There are many others with credentials better than mine who are are calling out the warning. So why bother again? Well the reason is: you can't have too many people warning you not to run with scissors! Perhaps my small contribution will be the last straw to make someone reconsider their multi-threaded application design. If so, my time has been well spent.

To the topic at hand.

Threads Increase Design Time

This is pretty simple. In the same way that it is infinitely easier to toss a single ball in the air as opposed to juggling three balls, it is easier to write a single threaded application than a multi-threaded application.

When designing a multi-threaded application you have many additional elements to consider:

  1. Concurrency

    • Each time you access memory you must carefully think through if it is possible that two threads will be executing this code segment at the same time.

  2. Performance

    • How do you insure that your threads are sleeping most of the time?

    • How do you prevent your threads from executing at the same time which will impare your performance?

    • Does your locking mechanism require a call to the kernal? If so are you prepared for the performance hit of getting locks?

    • Are you locking too much code for too long? Are you forcing your other threads to wait unnecessarily?


  3. Deadlock
    • If you have more than one lock can your threads deadlock?

    • What is your strategy for resolving/preventing deadlocks?


  4. State
    • Making an asynchronous call breaks the flow of your code. You make the call and expect a response at a later time. When you make the call you must save your state so that when the call eventually returns you can continue from where you left off. This is a major design consideration that is more often implemented poorly. Organizing your modules to have a single place where ansynchrounous returns are handled helps.
    • What happens when an asynchrounous call doesn't return? Are you tracking the time of your calls? What is your policy?

  5. Resource consumption and memory management
    • If one thread allocates memory, who deallocates it? Managing the lifetime of global resources (memory, file pointers, database connections) becomes especially difficult. Which thread "owns" the resource? How are you sure that all threads are done using the resource?

    • How many threads and locks are you using? These are kernel resources that are not free?

  6. Design for testing
    • Current debuggers are (and have been for over a decade) ineffective at tracking problems in multi-threaded applications. How will you debug your code? What tools will you use?

    • You will make use of extensive trace statements in your code (this is the answer to the previous question). What trace library will you use? How will you deploy your trace statements such that they don't significantly change the behavior of your code? What tools will you use to analyze the many megabytes of trace output produced?
It would seem that I am painting a bleak picture of multi-threaded development. Well I am, and it is well founded. This is one of the primary reasons that J2EE, COM+, and other Enterprise frameworks make you write single threaded code. These are highly multithreaded frameworks, but they make writing applications easier by hiding this fact.

No comments: