|
Personal Info:
Joe  leads the architecture of an experimental OS's developer platform, where
he is also chief architect of its programming language. His current mission is to enable
writing large-scale software that is reliable, secure, and scalable by-construction. Before this, Joe
founded the Parallel Extensions to .NET project.
He has been granted 19 patents, with 49 pending. When not working, Joe enjoys travelling with his wife,
writing books, writing music,
studying music theory & mathematics, and doing anything involving food & wine.
My books
My music
Disclaimer:
The content of this site are my own personal opinions and do
not represent my employer's view in anyway.
© 2012, Joe Duffy
|
|
 Thursday, January 26, 2006
Vance Morrison's excellent MSDN article from a few months back talks about why double checked locking is guaranteed to work on the CLR v2.0, and why it is one of the few safe lock-free mechanisms on the runtime. He also sent an email to the develop.com mailing list a while back explaining why this pattern wasn’t guaranteed to work on the ECMA memory model. We did quite a bit of implementation work and testing to tame the crazy memory model of IA-64 on 2.0. (Note that none of this is in the ECMA specification, so if you’re worried about CLI compatibility, beware.)
These modifications not only enable the double checked locking pattern, but also prevent constructors from publishing the newly allocated object before their state has been initialized, as I mentioned in my PDC presentation on concurrency last year. We accomplish this by ensuring writes have 'release' semantics on IA-64, via the st.rel instruction. A single st.rel x guarantees that any other loads and stores leading up to its execution (in the physical instruction stream) must have appeared to have occurred to each logical processor at least by the time x's new value becomes visible to another logical processor. Loads can be given 'acquire' semantics (via the ld.acq instruction), meaning that any other loads and stores that occur after a ld.acq x cannot appear to have occurred prior to the load. The 2.0 memory model does not use ld.acq’s unless you are accessing volatile data (marked w/ the volatile modifier keyword or accessed via the Thread.VolatileRead API). This can lead to some subtle problems.
For example, a slight variant of the double checked lock will not work under our model:
class Singleton { private static object slock = new object(); private static Singleton instance; private static bool initialized; private Singleton() {} public Instance { get { if (!initialized) { lock (slock) { if (!initialized) { instance = new Singleton(); initialized = true; } } } return instance; } } }
You might have decided to use this pattern to determine whether to initialize a value-type, since checking the variable for null isn’t possible. If you had some more complex set of state, perhaps you want to use a single Boolean rather than checking, say, 10 separate variables to see if they have each been initialized. Whatever your reasoning, as written the above code is prone to a subtle race condition.
The problem here is that both reads of initialized and instance do not have 'acquire' semantics. Thus, instance could appear to have been read before initialized, e.g. as follows:
| Time |
Thread A |
Thread B |
| 0 |
|
Reads instance as null |
| 1 |
Reads initialized as false |
| 2 |
Sets instance to ref to new obj |
| 3 |
Sets initialized to true |
| 4 |
Uses instance (initialized) |
| 5 |
|
Reads initialized as true |
| 6 |
|
Uses instance (null!) |
Thread B ends up returning a null reference. If a caller tried to use it, they might encounter a spurious NullReferenceException, the cause of which is incredibly hard to debug. For example:
void f() { Singleton s = Singleton.Instance; s.DoSomething(); // Boom! }
For this to have happened, Thread B would have had to read instance entirely out of order. It might have done so for any number of reasons. If it recently executed some code that pulled it into cache—either directly or due to locality—it isn’t required to invalidate the cache with non-acquire reads, even though it observed a new write with release semantics, because it's as if the load was moved before the load of initialized. Or superscalar execution might perform branch prediction and retrieve the value of instance, assuming that initialized will be false, pulling it into cache ahead of the read of initialized. Again, because it is a non-acquire read, this is a valid thing to do. If it reads initialized as true, its prediction was actually correct, and it just returns the null value that was pre-fetched. It might even be the case that a compiler along the way moved the read, which is also entirely legal with our memory model.
One possible solution for this is to employ a volatile-read on the first read of the initialized variable, prohibiting the read of instance from moving prior to the read of initialized. Control dependency prevents us from having to use a volatile-read for the reads of both variables.
class Singleton { private static object slock = new object(); private static Singleton instance; private static int initialized; private Singleton() {} public Instance { get { if (Thread.VolatileRead(ref initialized) == 0) { lock (slock) { if (initialized == 0) { instance = new Singleton(); initialized = 1; } } } return instance; } } }
You could have instead inserted a call to Thread.MemoryBarrier instead, which is a two way memory-fence, in between if-block and the read of instance, but the cost of a barrier is generally higher than both a st.rel and ld.acq because it affects surrounding instructions and movement in both directions.
The take-away here is not that you must understand the specifics of how cache coherency, speculative execution, and our memory model interact. Rather, it should be that once you venture even slightly outside of the bounds of the few "blessed" lock-free practices mentioned in the article mentioned above, you are opening yourself up to the worst kind of race conditions. Using locks is a simple way to avoid this pain. And hopefully someday in the future, transactional memory will enable performant execution of code with lock elision techniques that lead to the performance of lock-free code, but without any of the mental illness that such techniques have been proven to cause.
 Saturday, January 21, 2006
I drink about 4x more tea than I do coffee. Actually, I wouldn't drink coffee at all if there were decent tea stores in the area with readily available to-go offerings.
I just ordered a bunch of great teas from Upton Tea Imports, including a new 2006 Darjeeling First Flush. I can't wait until they arrive:
- TD56: Tindharia Estate FTGFOP1 First Flush (EX-1)
- TS70: Temi Estate FTGFOP1 CL
- TA93: Nahorhabi Estate FTGFOP1 SPL CL
- ZO87: Ginseng Tie-Guan-Yin Oolong
- ZM44: Osmanthus Oolong Se Chung
- ZW84: Organic Fuding White Treasure
- ZW90: Organic White Point Reserve
- ZW99: China White Paklum Tips Reserve
- TA98: Mothola Estate White Tea
- TJ77: Spring Harvest Kabusencha
If you're not a tea drinker, or the closest thing to real tea you've had is a soppy Stash tea bag, I encourage you to try one of Upton's sampler sets.
This deck, from a recent POPL presentation, offers a fascinating view on the future of mainstream games-programming languages, specifically around safety and concurrency: http://www.cs.princeton.edu/~dpw/popl/06/Tim-POPL.ppt.
(Of course, if I had a link to a video-taping of the talk or a detailed paper, that would be more useful; but I don't.)
An interesting conclusion--reinforcing a growing commonplace belief, at least here at Microsoft--is that there is no single concurrency model that works for all problems. In other words, there is no silver bullet. A combination of isolation (via STM), with implicit- and data-parallelism is discussed.
 Monday, January 16, 2006
Transactional memory promises to improve the lives of developers everywhere. From races, to deadlocks, to lock granularity and scalability headaches, the concept of transactions cleans up a lot of the worries inherent in the current lock-based concurrency programming model.
You have it today, sort of. Juval Lowy wrote a great piece on MSDN a few months back on how to build System.Transactions resource-managers over volatile in-memory data. I highly recommend checking it out. Maurice Herlihy has also made his SxM library available online here. I wonder if there's an interesting intersection between the two.
Update: I neglected to mention failure atomicity as one of the major benefits of TM, whether running concurrently or not.
 Tuesday, January 10, 2006
I recently uploaded a new page to serve as the online-companion for my .NET Framework 2.0 book.
It doesn't contain much right now, but it does have a lot of links to references I used throughout (books and articles): /books/netfx20/netfx20_book_resources.html.
 Saturday, January 07, 2006
I've posted before about how you might use C# enumerators to simulate coroutines. Enumerators are a very powerful feature, but unfortunately have one big drawback vis-à-vis their attempt at coroutines: you can yield only from one stack frame deep. The C# compiler state-machine transforms enough information for a single function, but obviously doesn't do that for the entire stack. Real coroutines can yield from an arbitrarily nested callstack, and have that entire stack restored when they are resumed.
There are other techniques. If you're willing to spend an entire thread to keep the stack alive, for example, you can use events to model coroutines with a standard producer/consumer relationship. The benefit to this approach is that you are in fact able to yield from arbitrarily nested frames. The clear drawback is the performance overhead. Each coroutine will eat up 1MB of reserved stack space from the virtual address space. But, probably worse, each time a new item is requested, an OS context switch is required; and similarly, whenever a new item becomes available (i.e. yielded), a context switch occurs again. This back-and-forth switching is pure overhead that could be eliminated with true coroutines.
(Note: this article describes how to use Fibers to avoid this context switch penalty. Fibers are dynamite on the CLR, however, so tread with caution if you even contemplate using this approach. Furthermore, you can easily dream up ways to serialize the physical stack, a la continuations. You do have access to the current CONTEXT, via GetThreadContext, on Windows and can use the thread's stack base and context ESP to determine the boundaries. But so many things in Windows rely on the TEB, from the CRT to exception handling to GetLastError to arbitrary usage of the TLS, like the way the CLR maintains a list of frame transitions. Nevermind having to accurately report roots back to the GC. These nightmares make real coroutines on Windows almost unapproachable, at least for the faint of heart.)
I hacked up a little Coroutine class this morning that uses the thread-per-coroutine approach mentioned above. Up front, I have to warn you: I spent 30 minutes on this thing. It's bound to be buggy, and I took some shortcuts (like not implementing the respective collections interfaces). Rather than walking through bit-by-bit, I've tried to comment the source code to explain how it works:
using System;
using SD = System.Diagnostics;
using System.Threading;
public delegate void CoroutineStart();
public class Coroutine<T> : IDisposable
{
// Fields
private CoroutineStart start;
private Thread thread;
private AutoResetEvent computeNextEvent;
private AutoResetEvent nextAvailableEvent;
private ManualResetEvent doneEvent;
private T current;
// We have a thread-static here so the coroutine needn't track the Coroutine<T> object
// manually. The Yield function is static, so they can just call Coroutine.Yield(v);
[ThreadStatic]
private static Coroutine<T> coroutine;
// Constructors
public Coroutine(CoroutineStart start)
{
this.start = start;
this.thread = new Thread(Worker);
this.computeNextEvent = new AutoResetEvent(false);
this.nextAvailableEvent = new AutoResetEvent(false);
this.doneEvent = new ManualResetEvent(false);
}
// Properties
public T Current
{
// TODO: we could add some error checking here, e.g. if somebody tries to
// read past the end-of-stream.
get { return current; }
}
// Methods
public bool MoveNext()
{
if (thread.ThreadState == ThreadState.Unstarted)
thread.Start();
else
computeNextEvent.Set();
// We wait on the 'next available' and 'done' events simultaneously. And then
// we use this to determine whether the coroutine has finished or not. The consumer
// will typically use this in a loop, e.g. while (c.MoveNext()) { f(c.Current); }.
return (0 ==
WaitHandle.WaitAny(new WaitHandle[] { nextAvailableEvent, doneEvent }));
}
private void Worker()
{
try
{
// Stash the coroutine object in TLS and start the CoroutineStart routine.
coroutine = this;
start();
}
catch (ThreadInterruptedException)
{
// Ignore the interrupt request. We use this as the 'proper' way to shut-down
// a couroutine. This is really a hack. Needs to be revisited.
}
finally
{
// Lastly, signal to the caller that the coroutine is done producing. Note that
// we'd ideally just use the thread executive object directly. But unfortunately the
// managed thread class doesn't expose this WaitHandle. :(
doneEvent.Set();
}
}
public static void Yield(T value)
{
Coroutine<T> c = coroutine;
// First, ensure we're on a coroutine thread.
if (c == null)
throw new InvalidOperationException("You can only yield from a coroutine thread");
// Now, set the coroutine's current value to the argument, signal to the consumer
// that we have a new item, and go to sleep until we're asked to compute the next item.
c.current = value;
c.nextAvailableEvent.Set();
c.computeNextEvent.WaitOne();
}
public void Dispose()
{
// We ensure the thread has stopped here. We use a really ugly interrupt to bring
// it down if not.
if (thread.ThreadState != ThreadState.Aborted &&
thread.ThreadState != ThreadState.Stopped &&
thread.ThreadState != ThreadState.Unstarted)
{
SD.Trace.TraceWarning("Coroutine thread has not stopped when Disposing, in state {0}",
thread.ThreadState);
thread.Interrupt();
// Joining here is questionable at best. It could lead to deadlocks.
thread.Join();
}
// Close out all of the events.
computeNextEvent.Close();
nextAvailableEvent.Close();
doneEvent.Close();
}
}
public static class Coroutine
{
// This is a trick. The C# compiler will infer the method argument <T>, enabling
// us to shunt right over to the Coroutine<T> implementation. This is nice because
// the user can just write Coroutine.Yield(n) instead of Coroutine<T>.Yield(n). The
// annoying part is that you can easily yield something of the wrong type, leading to
// an IllegalOperationException because C<T>.Yield will look in TLS and not find anything.
public static void Yield<T>(T t)
{
Coroutine<T>.Yield(t);
}
}
Now let's see it in action. Given a function Fibonacci, which continuously yields the next item in the Fibonacci sequence:
void Fibonnaci()
{
long n0 = 0;
long n1 = 1;
long n;
while (true)
{
n = n0 + n1;
n0 = n1;
n1 = n;
Coroutine.Yield(n);
}
}
We can form a coroutine over it and scroll through the first 10 numbers:
using (Coroutine<long> c = new Coroutine<long>(Fibonnaci))
{
int i = 0;
while (c.MoveNext() && i++ < 10)
Console.WriteLine(c.Current);
}
And of course, we can create a coroutine over a function that yields from functions deep in the call stack:
void a()
{
Coroutine.Yield("a");
b();
e();
}
void b()
{
Coroutine.Yield("a.b");
c();
}
void c()
{
Coroutine.Yield("a.b.c");
d();
}
void d()
{
Coroutine.Yield("a.b.c.d");
}
void e()
{
Coroutine.Yield("e");
}
And iterate over it:
using (Coroutine<string> c = new Coroutine<string>(a))
{
while (c.MoveNext())
Console.WriteLine(c.Current);
}
A neat extension to this whole idea might be a BeginMoveNext function that follows the asynchronous programming model. You could then exploit the fact that the consumer and producer are on separate threads to make progress while the producer is calculating the next item in line. Assuming you're on a multi-hardware-thread machine, this would cut down on the context switch penalty by as much as half.
 Thursday, December 29, 2005
 Wednesday, December 28, 2005
I wanted to increase the GoogleRank for Aaron's article: http://blogs.msdn.com/astebner/archive/2005/12/16/504906.aspx
My machine's Visual Studio 2005 installation has been partially hosed since somewhere around Beta2. Various tasks would regularly fail with "Package Load Failure" error messages. I could compile and perform basic functions with it, but was forced to use cmd-line for a lot of stuff. Like editing project files. This was caused by the multiple betas, CTPs, and hand-built CLRs that I've had installed over the past year; it seems some cruft had built up inside of the native-cache.
I followed his steps, and now it works like a charm. Sweet.
 Tuesday, December 27, 2005
Some fundamental changes were made in the .NET Framework 2.0 that just about obviate the need to ever write a traditional finalizer. A lot of the guidance written here is now obsolete, not because it is incorrect, but rather because there is one important new consideration to make (hosting) and a set of new tools to aid you in the task. Jeff Richter pointed this out to all of us a few months back.
As Stephen Toub discusses in depth in his recent MSDN Magazine article on CLR reliability, resources not under the protection of critical finalizers are doomed for leakage when run inside of sophisticated hosts. SQL Server uses AppDomains as the unit of code isolation, much like Windows’ use of processes. When it tears one down, it expects there to be no resulting residual build-up over time. But if the best you’ve got are ordinary finalizers to clean up resources, a rude AppDomain unload can bypass execution of them entirely, leading to leaks over time. This might happen if a finalizer in the queue with you takes too long to complete, perhaps by deadlocking on entry to a non-pumping STA, causing the host to escalate to a rude unload.
Critical finalizers
During a rude unload, normal finalizers are skipped, finally blocks aren’t run, and only critical finalizers get a chance to make the world sane again. Thus we can immediately form a guiding principle:
Any resource whose natural lifetime outlasts an AppDomain must be protected by a critical finalizer to avoid leaks.
Notice that I say "lifetime spans an AppDomain." This is important. Finalizers are often used for process-wide resources, such as file HANDLEs and Semaphores. But a resource whose lifetime is limited to the enclosing process’s surely outlasts any single AppDomain; a finalizer is not good enough. Another piece of code in the same process might be denied access to the file handle because the (now-dead) AppDomain orphaned an exclusively-opened handle to it. Windows ensures this HANDLE will get released when the process shuts down, but our goal with critical finalization is to do this at AppDomain unload time (avoiding cross-AppDomain interference). In the worst case, not doing so can actually lead to state corruption; a process crash is then likely to ensue, taking down a host like SQL Server with it. Imagine if two AppDomains—perhaps even multiple processes—communicate via memory mapped I/O inside of a shared address space. If an AppDomain gets interrupted by an unload mid-way through a paired operation and intends to clean up state in its finalizer, failure to execute the finalizer might lead to chaos. A critical finalizer should have been used. (And use of BeginDelayAbort, e.g. via a CER. But that’s digging a little too deep for now.)
Critical finalizers are somewhat easier to write when compared to ordinary finalizers, due to the out-of-the-box plumbing that you get. But they impose additional constraints on what you can actually do at finalization time. To implement a critical finalizer, simply subclass the System.Runtime.ConstrainedExecution.CriticalFinalizerObject (CFO) type, provide a way for users to acquire a resource (e.g. in the constructor), and override its Finalize method to perform cleanup. When instantiated, your object will be placed onto the critical-finalization object queue. CFOs can be suppressed as usual with the GC.SuppressFinalize method, and can be re-registered onto the critical-finalization queue with the GC.ReRegisterForFinalize method. The CLR then ensures your object is finalized should a rude unload occur; obviously, it also runs them in the same cases ordinary finalizers are run too: i.e. standard GC finalization, managed shut-down, ordinary AppDomain unload, etc. There is a weak guarantee that CFOs are finalized after other finalizable objects, specifically to accomodate relationships like how the FileStream must flush its buffer before its underlying SafeHandle has been released.
As noted, writing a CFO Finalize method is trickier than a standard finalizer due to additional constraints. This is because it can be called from inside of a CER if the host escalates to a rude unload. It must guarantee that state will not be corrupted as a result of its execution and that it will never fail (i.e. by leaking an exception). And of course you can only call non-virtual methods that make similar guarantees. This means your code has to be written to succeed in the most hostile of situations, for instance in situations where any attempt to allocate memory dynamically will be rejected via an OutOfMemoryException. If you let that exception leak, you’ve violated the contract and can expect the host to respond in any number of ways, including crashing the process immediately. CERs perform eager preparation to statically ensure your code can execute, jitting the transitive closure of methods you invoke, but it’s easy to make a misstep here due to the massive number of hidden allocations in the runtime. A box instruction allocates memory; unbox does, too, but only if you’re unboxing a Nullable<T>; throw has to manufacture a RuntimeWrappedException if you're throwing a non-Exception object; and so forth. And unfortunately there aren’t any tools to prove that you’ve written your CER correctly. Thankfully most developers write bug free code on their first attempt. ;)
Critical- and safe-handles
Using the base CFO type directly has a couple drawbacks. First, it doesn’t fully implement the IDisposable pattern. There are two convenient Framework CFO abstract classes that do, both in the System.Runtime.InteropServices namespace: CriticalHandle and SafeHandle.
The CriticalHandle type is sufficient to get critical finalization semantics: you simply override its protected constructor and ReleaseHandle methods, performing open and close operations inside of them respectively. Your ReleaseHandle implementation can be called from inside of a critical finalization CER, so as with writing CFOs by hand you must make the same guarantees outlined above. This type provides a cleanly factored and encapsulated interface to your users.
But more concerning is the fact that both CFO and CriticalHandle are still prone to security problems that you might need to worry about if you’re building any sort of reusable Framework. BrianGru outlines this situation here. To tackle those issues, you need SafeHandle. Implementing SafeHandle is much like CriticalHandle, in that you override the protected constructor and ReleaseHandle methods, and abide by CER rules inside of ReleaseHandle. One additional piece is necessary, however: you must implement the abstract IsInvalid property getter and return true or false to indicate whether the SafeHandle refers to an invalid handle. (The SafeHandleMinusOneIsInvalid and SafeHandleZeroOrMinusOneIsInvalid types in the Microsoft.Win32.SafeHandles namespace are there to help out here, returning true if the handle is the value -1 in the first case and true if the handle is the value -1 or 0 in the latter case. A PVOID with a value of 0 (i.e. NULL), for example, would be invalid for a handle to a memory address; SafeHandleZeroOrMinusOneIsInvalid would be perfect for this.) ShawnFa discusses implementing SafeHandle in more detail on his blog.
Your CriticalHandle and SafeHandle types should never take on additional business-logic responsibility; make them as light-weight as possible, doing just enough to allocate and free resources. You’ll probably have a number of other functional classes that make use of these handles. The Framework’s Stream types are a classic example. Such types should implement the IDisposable interface and invoke Dispose on the underlying handle, providing an eager way to dispose of the resource. They should furthermore take care to never publicly expose the underlying handle, as doing so could be used to erroneously suppress finalization on a handle, leading to resource leaks.
Did you really mean never?
Almost. There are still several situations in which people must still write complex finalizers. The tax they must pay for stepping outside of the simple allocate/deallocate pattern is understanding intimately the big mess outlined here. Most people should consider factoring their real cleanup code to use a SafeHandle, and only then layering specialized code on top of that inside of a normal finalizer.
After a brief email thread with Chris Brumme, a number of legitimate cases of alternative finalizer patterns were identified, including:
- Sophisticated APIs can use finalizers to return expensive objects—like large buffers or database connections—back to a pool, amortizing the cost of creating and destroying them over the life of the application. System.EnterpriseServices does this. This is one of the only cases where resurrection is an acceptable practice. Critical finalization should only be used here if resources are pooled across an entire process. Most resources are AppDomain-local, and thus do not qualify for CFO status.
- Calling GC.RemoveMemoryPressure to compensate for a previous GC.AddMemoryPressure, used to communicate to the GC that the pressure associated with an object's resources is no longer a factor (because it's been cleaned up). This should be protected by a CFO if the resource whose pressure it tracks is also allocated/deallocated under a CFO. It’s unfortunate that the RemoveMemoryPressure API doesn’t make reliability guarantees (e.g. with ReliabilityContractAttribute). If it attempts to allocate memory—I can’t imagine that it would—you could end up crashing the process due to an unhandled OutOfMemoryException. You could consider swallowing such exceptions, at the risk of violating the corruption contract. This is a crappy situation, but if a large quantity of pressure were leaked after an AppDomain unload, a skew could build up over time, affecting all parties in the process, precisely what we’re trying to avoid by using CFOs. You need to make an intelligent tradeoff. We should fix this in a later release.
- Incrementing or decrementing a performance counter or lighter-weight counter like a static field. This is often used to monitor the rate of creation/destruction of objects, and is often turned off in retail builds. Assuming imprecise counting is OK—e.g. if it’s used only for testing purposes—this should not use a CFO. If you do use a CFO, you have you follow the guidelines above. For light-weight counters this is easy (i++ and i-- traditionally don’t allocate memory); but for performance counters it is not.
- Asserting to find cases where an object should have been, but was not, eagerly cleaned up using the IDisposable pattern. Properly written eager disposal is supposed to call GC.SuppressFinalization to eliminate the assert. It would be inappropriate to use CFOs for this purpose. Finally blocks will not run under a rude unload (which includes Dispose methods), and thus under any rude unload situation your CFO will fire.
- Some external resources have elaborate rules for sequencing cleanup. The COM ADO APIs (not ADO.NET) require that fields are cleaned up before rows, which must precede tables, which must precede connections. If objects are cleaned up in a free-threaded manner or in the wrong order, memory corruption will occur. In other words, they violate the standard COM pUnk AddRef/Release rules. Outlook exposes COM APIs with similar sequencing rules. This is traditionally addressed by writing elaborate finalization code that walks the graph on the managed side and initiates the sequenced cleanup. This is the trickiest of all. If you can guarantee you follow the CFO rules outlined above, this probably belongs in a critical finalizer. But it’s quite easy to make a misstep...you're basically playing with dynamite at this point.
If you decide you must write a finalizer, it’s still important to follow the pattern described here, or the condensed version in the .NET Framework Design Guidelines book. This facilitates seamless integration with VC++ 2005’s new destructor, Dispose, and finalization unification features.
Summary
At first glance, it appears that the world is simpler with CFOs. But when you consider that you have to abide by the same rules for normal finalizers plus the new ornery CER rules, life still isn’t very simple at all. CriticalFinalizerObject makes sure your resources don’t leak during hostile takeovers, and SafeHandle makes life more secure and a little easier in that the plumbing required to get IDisposable hooked up is all built for you, but one thing remains the same: Interoperating with unmanaged code is tricky stuff. But thankfully the world will be written in nothing but managed code sometime in the near future. Then we can get rid of all of this hairy finalization code once and for all.
 Tuesday, December 20, 2005
Now that the book's quieted down, I have more time to do things like code, read, drink wine, eat, and sleep. In that order.
And I've changed roles at Microsoft to focus entirely on concurrency.
I'm wrapping up development on some C++ code I wrote for an upcoming MSDN article. I also intend to spend quite a bit of time over the holiday finishing up another project that has really tested my thinking and coding skills. I love stuff like that. When carefully and intentionally crafted code must play nicely with the topology of the underlying machine. I have a presentation to the C# Design Team in late January to show 'em what I got, so I need to get these ideas down into code and optimized ASAP.
I've also become quite hooked on the sweet sounds of System of a Down. My 5 Top Played bands in iTunes right now are (in order): System of a Down, Ill Nino, Mudvayne, Machine Head, and Misfits. I've also been playing a bit more guitar lately, recording a little, but not being overly happy with the end result. Someday.
By the way, if you want to buy me any books, here's a condensed Wish List:
Interestingly, a number of those authors currently work at Microsoft.
Have a happy holidays everybody.
|
|
Recent Entries:
Search:
Browse by Date:
| | Sun | Mon | Tue | Wed | Thu | Fri | Sat | | 25 | 26 | 27 | 28 | 29 | 30 | 31 | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | | 8 | 9 | 10 | 11 | 12 | 13 | 14 | | 15 | 16 | 17 | 18 | 19 | 20 | 21 | | 22 | 23 | 24 | 25 | 26 | 27 | 28 | | 29 | 30 | 31 | 1 | 2 | 3 | 4 |
Browse by Category:
Notables:
|