Prefer Using Active Objects Instead of Naked Threads
Herb Sutter is a bestselling author and consultant on software development topics, and a software architect at Microsoft. He can be contacted at www.gotw.ca.
As described in Use Threads Correctly = Isolation + Asynchronous Messages[1]), to use threads well we want to follow these best practices:
- Keep data isolated, private to a thread where possible. Note that this doesn't mean using a special facility like thread local storage; it just means not sharing the thread's private data by exposing pointers or references to it.
- Communicate among threads via asynchronous messages. Using asynchronous messages lets the threads keep running independently by default unless they really must wait for a result.
- Organize each thread's work around a message pump. Most threads should spend their lifetime responding to incoming messages, so their mainline should consist of a message pump that dispatches those messages to the message handlers.
Using raw threads directly is trouble for a number of reasons, particularly because raw threads let us do anything and offer no help or automation for these best practices. So how can we automate them?
A good answer is to apply and automate the Active Object pattern [2]. Active objects dramatically improve our ability to reason about our thread's code and operation by giving us higher-level abstractions and idioms that raise the semantic level of our program and let us express our intent more directly. As with all good patterns, we also get better vocabulary to talk about our design. Note that active objects aren't a novelty: UML and various libraries have provided support for active classes. Some actor-based languages already have variations of this pattern baked into the language itself; but fortunately, we aren't limited to using only such languages to get the benefits of active objects.
This article will show how to implement the pattern, including a reusable helper to automate the common parts, in any of the popular mainstream languages and threading environments, including C++, C#/.NET, Java, and C/Pthreads. We will go beyond the basic pattern and also tie the active object's private thread lifetime directly to the lifetime of the active object itself, which will let us leverage rich object lifetime semantics already built into our various languages. Most of the sample code will be shown in C++, but can be directly translated into any of the other languages (for example, destructors in C++ would just be expressed as disposers in C# and Java, local object lifetime in C++ would be expressed with the using statement in C# or the "finally dispose" coding idiom in Java).
Active Objects: The Caller's View
First, let's look at the basics of how an active object is designed to work.
An active object owns its own private thread, and runs all of its work on that private thread. Like any object, an active object has private data and methods that can be invoked by callers. The difference is that when a caller invokes a method, the method call just enqueues a message to the active object and returns to the caller immediately; method calls on an active object are always nonblocking asynchronous messages. The active object's private thread mainline is a message pump that dequeues and executes the messages one at a time on the private thread. Because the messages are processed sequentially, they are atomic with respect to each other. And because the object's private data is only accessed from the private thread, we don't need to take a mutex lock or perform other synchronization on the private shared state.
Here is an example of the concurrency semantics we want to achieve for calling code:
Active a = new Active(); a.Func(); // call is nonblocking … more work … // this code can execute in parallel with a.Func()
Next, we want to tie the lifetime of the private thread to the lifetime of the active object: The active object's constructor starts the thread and the message pump. The destructor (or "disposer" in C# and Java) enqueues a sentinel message behind any messages already waiting in the queue, then joins with the private thread to wait for it to drain the queue and end. Note that the destructor/disposer is the only call on the active object that is a blocking call from the viewpoint of the caller.
Expressing thread/task lifetimes as object lifetimes in this way lets us exploit existing rich language semantics to express bounded nested work. For example, in C# or Java, it lets us exploit the usual language support and/or programming idiom for stack-based local lifetimes by writing a using block or the Dispose pattern:
// C# example
using( Active a = new Active() ) { // creates private thread
…
a.SomeWork(); // enqueues work
…
a.MoreWork(); // enqueues work
…
} // waits for work to complete and joins with private thread
As another example, in C++ we can also express that a class data member is held by value rather than by pointer or reference, and the language guarantees that the class member's lifetime is tied to the enclosing object's lifetime: The member is constructed when the enclosing object is constructed, and destroyed when the enclosing object is destroyed. (The same can be done by hand in C# or Java by manually wiring up the outer object's dispose function to call the inner one's.) For example:
// C++ example
class Active1 { // outer object
Active2 inner; // embedded member, lifetime implicitly
// bound to that of its enclosing object
};
// Calling code
void SomeFunction() {
Active1 a; // starts both a's and a.inner's private threads
…
} // waits for both a and a.inner to complete
That's what we want to enable. Next, let's consider how to actually write a class with objects that are active and behave in this way.
Array Building Blocks: A Flexible Parallel Programming Model for Multicore and Many-Core Architectures
Array Building Blocks provides developers with advanced data-parallel programming capabilities
This Week's Multicore Reading List
Advanced Computer Science and Ruby and RailsPerformance Optimization for the Atom Architecture
ParBenCCh 1.0 Parallel C++ Benchmarking Suite
- Quick Evaluation Guide: Locate a Hotspot and Optimize It
- Quick Evaluation Guide: Eliminate Memory Errors and Improve Program Stability
- Quick Evaluation Guides: Optimize an Existing Program by Introducing Parallelism
- Case Study RTT: Driving Automotive Design Innovation, Efficiency, and Cost Savings
- Case Study Sentinelle: Achieving Early and Accurate Detection of Cancer
- Intel Parallel Studio; Download the free eval today!
- Parallelism Breakthrough Video Series; Watch and learn more about Intel® Parallel Studio
- 2009 Intel Software Webinar Series; View On-Demand webinars
- Coding for Multi-core Processes; Intel® Compiler Pro eBook
- Performance Through Parallelism; Intel® Tuning for Vista eBook
- Intel® Software Network; Connect with developers and Intel engineers
-
September 13, 2010
The goal of the Third International Workshop on Parallel Programming Models and Systems Software for High-End Computing is to bring together researchers and practitioners in parallel programming models and systems software for high-end computing architectures. Please join us in a discussion of new ideas, experiences, and the latest trends in these areas at the workshop.
Parallel Architectures and Compilation Techniques
-
September 11-15, 2010
The International Conference on Parallel Architectures and Compilation Techniques (PACT) is a premier international forum for the presentation of research results in parallel computing. As a multi-disciplinary conference that brings together researchers from the hardware and software areas, PACT brings together researchers and practitioners in parallel systems to present ground-breaking research related to parallel systems ranging across instruction-level parallelism, thread-level parallelism, multiprocessor parallelism and large scale systems.
IDF2010
-
September 13-15, 2010
The Intel Developer Forum 2010 is your opportunity to collaborate with thousands of key industry players. Hear from more than 150 leading technology companies from around the world. Ask questions, get answers, experience live demonstrations, and more. Between the highly informative Keynotes, Technology and Industry Insights, Intel Fellows Live & Uncensored and Technical Sessions (including lectures, interactive panels, hands-on labs and Hot Topic Q&As), this year's IDF has everything you need to stay on top of the latest technology trends.
-
February 12-16, 2011
The Symposium on Principles and Practice of Parallel Programming is a forum for leading work on all aspects of parallel programming, including foundational and theoretical aspects, techniques, tools, and practical experiences. In the context of the symposium, "parallel programming" encompasses work on concurrent and parallel systems (multicore, multithreaded, heterogeneous, clustered systems, distributed systems, and large scale machines). Given the rise of parallel architectures into the consumer market (desktops, laptops, and mobile devices), PPoPP is particularly interested in work that addresses new parallel workloads, techniques and tools that attempt to improve the productivity of parallel programming, and work towards improved synergy with such emerging architectures.



Most recent comment
which template?
Comment by hendrixj Sep 2, 2010, 09:35 AM EDT
The text of the template in question got messed up by the text processor for the comment. Let me try again:
typedef template<void()> Message