More threading fun!

| | Comments (18)

One concept in threads programming is the idea of a "future." The concept is really quite simple to think about, but poses an interesting problem in some languages.

Basically, a future is a special data type that promises to have a value set sometime in the future. The easiest way to think about this is to think in terms of when access occurs.

If threadA comes and calls Future.Set, everything just continues executing as normal. Then, if threadB comes and called Future.Get, the value has been initialized, so everything keeps moving right along.

However, if threadB were to come along and call Future.Get, but that future is uninitialized (no one has Set the value yet), then threadB would be put to sleep until the future becomes initialized.

In this fashion, a future is like a promise that a particular property will always be initialized before you can actually use it.

Pretty neat concept, eh?

REALbasic has no built-in concept of a Futures data type. However, the resourceful programmer can easily make a Future class to meet their needs. Let's break the problem down into its individual parts to arrive at a solution.

  1. We need a way to tell when a value has been initialized. That's simple -- we can just use a boolean flag that gets set to true when someone assigns to a property.

  2. So now we need a way to tell when someone assigns to a property so that we can set our flag. That's easy too -- we can use a computed property. That has a Get and Set method that we can utilize.
  3. Now we need a way to tell when the user tries to access the property. We've already solved this however -- we're going to use a computed property.

  4. We need some way to lock the calling thread if it calls Get and the value isn't initialized yet. Again, we can do this without worry. REALbasic has a Semaphore object which we can use.

  5. We need a way to unlock threads once the value has been set. Again, we'll be using the Semaphore.

Because of REALbasic's cooperative threading, we don't have to worry about what happens if a thread pre-empts another one in the middle of some operation. So it sounds like we can easily do exactly what we're after (which is what I'd hope since I don't usually write blog posts the trail off into mumblings). Awesome! Let's check out this class.
[rbcode]
Class Future
// We set this to true once we initialize
// the value.
Private mInitialized As Boolean

// This is the lock used by the Value computed
// property's getter and setter.
Private mLock As Semaphore

// This is the backing storage for the class. It's used
// by the Value computed property
Private mValue As Variant

Sub Constructor()
// The object starts out uninitialized
mInitialized = false

// We want to make a new semaphore
// so that we can block threads when
// needed.
mLock = new Semaphore

// The semaphore starts out originally
// locked so that any thread calling Get
// will have to block. If someone has
// already called Set though, then the
// call to Get won't ever try to lock the
// semaphore, so it's fine.
mLock.Signal
End Sub

Property Value As Variant
Function Get() as Variant
// If the value hasn't been initialized
// yet, then we need to lock ourselves
// until someone calls Set
if not mInitialized then mLock.Signal

// At this point, the value is initialized, so
// we can just return it
return mValue
End Get

Sub Set( vakue as Variant )
// Set the value
mValue = value

if not mInitialized then
// The value is now initialized, so flag it
mInitialized = true

// We need to release as many locks as we can
// because there could be multiple locks obtained
// from other threads calling Get.
while not mLock.TrySignal
mLock.Release
wend

// At this point, we own the lock again, but we
// know there's only one lock. So release it. This
// means that the semaphore is now back to the
// unlocked state
mLock.Release
end if
End Set
End Property
End Class[/rbcode]
Well that wasn't so hard, now was it? Everything worked out just like we said it would. If a thread attempts to access the Value computed property before it's been initialized, that thread will be locked by the Semaphore. When a thread finally gets around to setting the Value computed property, all other threads that were locked are then unlocked (so they can finally access the value).

I've created a simple sample project called FuturesExample which demonstrates this class in action. When you run this project, open up your system's DebugLog viewer as well. If you click on the first two push buttons, you'll see that nothing interesting happens. That's because they're both locked! Pressing the third button causes the Future to be initialized, where upon the other threads wake up and can do their thing.

And that's your neat geek trick for the day. You can thank Mars for getting me to think about this problem and come up with the solution. He and I had a very interesting discussion today about some of the future (no pun intended) directions we'd like to take threads in. This just so happened to be the last thing we talked about, and one of the things that was possible to implement in REALbasic without any framework help.

18 Comments

After coming up with this solution, Mars pointed out an optimization I could make in the Value Setter. Basically, check the state of mInitialized and don't do the Signal/Release dance if you don't need to. I've corrected the sample project.

And I just now fixed the example in the post itself.

Uh, and the 3 propeller heads on the planet who understand this stuff are happy you did.,

Hey, if it makes three people happy, then I'm glad I did it. :-P

Amazing concept. I've not heard of such a thing. I can think of so many freaking places I could use this where I've got dirty multi-variable-dependencies in place. Such a thing would really make my life so much easier.

That's why...tomorrow...I shall be building this class. :D

Once again, Aaron has come to the rescue!

"I don’t usually write blog posts the trail off into mumblings"
I can name one ;)

Seriously though, thanks for this post. It'll make a tremendous addition to my toolkit.

Cool. One thing that popped to mind, but I'm now not sure it would work - could you just make a new semaphore instead of all the releases? So, instead of the while loop and final release, just do:

mLock = new semaphore

At first it seemed like that would work, but looking at it now I'm not so sure. Is the mLock.Signal in the other thread considered a reference to the old semaphore? If not, what happens to someone who is waiting on a semaphore that gets destroyed?

Well I'll be dipped! I thought I was the only one who thought this sort of stuff was awesome. :-)

@Brady -- I do believe the behavior is entirely undefined as to what happens when you set a semaphore to nil while other threads are blocked on it. But... I'll define what it's GOING to do in r2 (in about fifteen minutes): calling Signal (or Enter) will increment the reference count, and calling Release (or Leave) will decrement it. That way you can't ever destroy a locking mechanism with blocked threads on it.

Heh - good answer :)

There's a typo:
Sub Set( vakue as Variant )

Also, it seems semaphores are slightly broken on OS X -- they use 100% CPU when waiting on signal()! Try running your example program and just clicking the thread 1 button...

Neat article!

That's strange -- they don't do that on Windows, but I see that it does do that on the Mac. Something worth looking into; thanks for pointing it out!

I've never really understood why people care so much about the CPU usage figures reported by top. I think it must just be a case of optimizing what you can measure, since nobody seemed to care before OS X came out.

@Mars - you must not have a Titanium PowerBook. Not only does it suck my battery life when unplugged, but it makes the bottom of the machine a near-molten pool of industrial strength titanium. I keep activity monitor open with the CPU activity strip floating around just to see if anything is mis-behaving. If it is, it receives an unceremonious quit (sometimes forced). If I catch it soon enough, I can preempt the heat and fan noise. I didn't have these problems before OS X, because it wasn't practical to have more than 1 or 2 apps open at a time. Oh, and I didn't have a Titanium laptop :)

Yeah, even if you don't have a Powerbook but have one of the hot G4 or G5 PowerMacs the CPU can make a difference... nobody likes a noisy/hot computer.

I guess I can see how processor usage would matter, if you are paying attention to battery life. I do have a Titanium PowerBook, and it was actually my main development machine for several years, but it doesn't sound like my experiences were much like yours. I never rely on the battery for anything other than preserving sleep state between outlets, I never actually put the machine on my lap, and I delayed the switch to OS X as long as possible.

I don't think the CPU should tank just because a thread is suspended or sleeping, but what's goofy is that it happens at all. Our thread scheduler is cross platform, and this behavior definetely does NOT happen on Windows. So if the scheduling is cross-platform, then why would this behavior be present unless it's some goofy OS interaction?

It's certainly worth looking into (I threw it on my "check this out when you get the chance" list).

@Phil -- speak for yourself. My computers kick out enough heat that it actually drops my gas bill for the furnace. ;-)

A more complete bug report on the CPU hammering bug is here:

http://realsoftware.com/feedback/viewreport.php?reportid=ymfubvcy

Leave a comment

Disclaimer

I'm currently an employee of REAL Software. My blog is mine. The opinions represented in this blog are mine as well and may not represent my employer's opinions. All original material is copyrighted and property of the author.

REALbasic® is a registered trademark of REAL Software, Inc. REAL SQL Server™ and Lingua™ are pending trademarks of REAL Software, Inc. All rights reserved.