Earlier this evening I gave my dad a birthday card that said "Celebrating the year you had to walk 8 miles through the snow to be born." Having grown up hearing my fair share of "When I was a boy..." stories that all pointed out how easy I had it compared to what he had to deal with growing up, I thought it was kind of funny. Then tonight, I found myself responding to a frantic plea from someone that needed help tracking down some issues in their multithreaded forms application. I won't go into the details of what I found when I looked into things, but I'm fairly certain they'd have no trouble qualifying for Fox's next episode of it's new high tech reality show, "When Threads Attack". But this little episode (which is not uncommon in my line of work) got me thinking. How on earth could these people not get this stuff working with all the cool things .NET gives us in the async space - like integrated thread pools, 4 letter keyword-based locking, and named threads? Why, when I was a boy...
About 12 years ago, I was building software that connected DOS applications to fax devices that were either connected to the PC via a bidirectional parallel port or reachable across a Novell network. So I was working on modern stuff like DOS device drivers and TSRs that were loaded at PC startup via config.sys and autoexec.bat. Asynchronous execution was achieved by hooking real mode interrupt 8 (the PC clock), and doing work a slice at a time; paying careful attention to the preservation of registers. This allowed us to send and receive faxes in the background w/o interfering with the normal operation of whatever program the user was interacting with (eg: Wordstar). The programming language of choice was Intel x86 assembly, and we debugged things by hooking up an oscilloscope to the serial port. Each routine in the ISR wrote a different binary value to COM1, "lighting up" different pins on the port. The varying patterns that resulted on the scope told me where my code was/wasn't and what it did last before choking. Sure, some of my Unix friends had pthreads and got to program in C, but that was for sissies that couldn't handle "real" async programming.
Not long after that, I left for the greener big company pastures of Intel to work on realtime conferencing systems for the PC platform. By now, Windows 3.0 had gained a lot of momentum but, unfortunately for us, didn't support pre-emptive multithreading (a distinct step backwards from DOS as far as I was concerned :-). Luckily for us, a group of really bright people upstairs in the research labs had ported a realtime kernel to Windows as a set of VxDs. This kernel, which completely took over Windows during boot, provided us with a preemptive scheduler that supported the notion of prioritized "tasks". Windows itself became the kernel's idle thread. So only when all of our tasks settled down did Windows get to run. This was ideal for an audio/video conferencing system, because it meant that PC users couldn't cause our communications subsystems to lose their connections by simply grabbing the mouse and dragging a window around the screen like they were doing before we discovered the kernel upstairs.
Having evolved myself by now from assembly to C to C++, I was feeling my oats as an OO programmer and decided that I couldn't live without C++ in kernel mode. Just imagine what I could do with a C++ Task class that abstracted away the minutae of multitasking in kernel mode! Trouble was, the compilers and linkers didn't support C++ development of VxDs at the time (this was before products like VtoolsD were invented), so I felt compelled to write the necessary startup code and supporting new/delete/etc operators that enabled me to introduce C++ to VxD land. This was wonderful, except that the kernel mode debuggers at the time only supported C. But that was a small price to pay in exchange for having a C++ Mutex class.
Eventually, Windows NT shipped and I graduated to Win32's version of multithreading. Finally - Windows had caught up with the rest of the world by offering a preemptively scheduled multithreaded world for asynchronous programming. Between that and honest to goodness virtual memory, I was in async heaven. Microsoft had brought multithreading to the masses - providing cool functions like CreateThread, CreateMutex, and InitializeCriticalSection. What could be simpler? Of course, I was a little worried that since Microsoft had just made multithreading so accessible to the general developer community, that my job security had taken a hit. Surely everyone would start building highly scalable, responsive applications now.
It wasn't long, however, before I found myself spending less time writing my own code and more time wandering around from cube to cube helping other developers fix multithreading bugs in their parts of the system (at one point, the project I was working on had something like 200 programmers involved). In an effort to make multithreading a little more approachable, I wrote various versions of thread synchronization wrapper classes (like CMutex, the new and improved replacement for Mutex) and a custom thread pool (to address the thread mania mess my colleagues had gotten themselves into). Things progressed slowly at this point; the only highlights being NT 3.51's introduction of the I/O completion port, and Windows 2000's introduction of a built-in threadpool. At some point in here, MFC graduated to Win32 and provided its own CMutex and highly value add methods like AfxBeginThread. But I was never much of a button boy, so I tried to ignore MFC except when colleagues needed help with multithreading in their MFC apps.
And who could forget COM? At some point during all of this, COM came along and gave birth to a wonderful construct known as the apartment. I couldn't believe how easy Microsoft was making it for programmers to "do threads" right. Here was a platform that let you build a component without worrying about what the thread-savvy application developers were doing around you, and just use a little registry setting to tell COM they had to deal with it for you. How much easier could it get?
Of course history (and lots of therapy bills) subsequently proved that multithreading was still too difficult to get right for the bulk of the developer community. But then .NET came along...
With .NET, Microsoft programmers were provided with the warm and inviting pink padded cell of the CLR within which they could finally practice safe multithreaded programming. Here was a runtime that provided a builtin thread pool with a type-safe interface in the form of Delegate.BeginInvoke - the magical partnership between the compiler and runtime that was destined to make put multithreading within easy reach of programmers everywhere. Microsoft even provided a multi-reader, single-writer lock for us! And if that weren't nice enough, the Windows Forms team was kind enough to provide us with ISynchronizeInvoke - a work around for the age-old problem of Windows having thread affinity (although they lost points for giving its members names like BeginInvoke and Invoke, which are not to be confused with the same-named members of the Delegate class).
Which brings me to tonight. After all this time, why is it that multithreading is still so difficult to get right for most programmers? I could waste lots more of your time discussing the answer in the small, but I realized tonight for the first time that the biggest barrier to "doing threads right" is letting programmers touch them.
Think about it - web application developers everywhere routinely build scalable, responsive systems that leverage the wonders of things like I/O completion ports, threads, and pooled resources. But they do it without (in many cases) even realizing those constructs are involved. ASP.NET/IIS/http.sys handles the async I/O and thread pooling necessary to efficiently pull a request off the wire and dispatch it into the application. The ASP.NET application developer then fiddles around with some stuff in memory (like strings) and proceeds to leverage a pooled connection to issue a query to a database, where something like SQL Server again uses async I/O and fancy threading techniques to formulate and send back a query result. At this point, the app developer then happily binds to a control before letting ASP.NET/IIS/http.sys again use async I/O to write the response buffer back to the client.
ASP.NET applications are the only successful examples of (Microsoft-based) multithreaded applications being developed by the general purpose Microsoft developer community. And it happened because ASP.NET takes multithreading out of our hands. Hmm...
I've been side tracked lately working on speech applications for Microsoft Speech Server, so I haven't had much of a chance to dig into Whidbey to see whether Microsoft has brought ASP.NET's pay-no-attention-to-the-thread-behind-the-curtain approach to multithreading to the desktop forms-based application. It might be there. But if it's not, I sure hope they're working on it for Longhorn.
Posted
May 26 2004, 08:08 PM
by
mike-woodring