Wednesday, December 07, 2005

T&L - Not Made For Each Other?



Threading libraries hide very little of the complexity involved in the use of threads. Often, the focus and the major obstacles in implementing a multithreaded system are the low-level details of threading, rather than the problems that the system is designed to solve.

Objects can be said to model reality, thereby providing good abstraction. However, a crucial part of reality that is not represented adequately in an object is a sense of time. Objects exist in time and space, and interact with other objects; they have "life". A mechanism for intuitively expressing such Live Objects (or Active Objects), where an object has its own thread, is missing from the threading libraries in (libraries of) object-oriented languages today.
Spatial representation OK.
Temporal representation, not OK.

Presently to use threads, we create a thread and give it sections of code (from the object) for it to do. For example,

threadHandle=CreateThread(StuffToDo , OS/LibraryParameters)

is how threads of control are created from the program. The code (StuffToDo) is then executed in the newly created thread.

2.1 Observations

In the present library implementations,

2a Threads use objects.
The thread executes a function, possibly a function or method of an object. In the example, StuffToDo could be the method of an object.

2b Threads are to functions what objects are to classes.
Or, threads are objects. The thread object instantiates a function, brings into execution-space so to say, executing it. The implementation forces the thread to become a first-class "object" in the program-space. It has a handle, threadHandle, that can be used to refer to it.

2c Threads are almost equivalent to operating system threads.
Most of what can be done with an operating system thread can be done with library threads also - one can suspend a thread, resume it, abort it etc


In the real world, entities use their time and their resources to do things. A thread can be viewed as a flow of time for practical purposes. In the library-model, threads use objects (2a) whereas in reality, objects use threads. This unnatural modeling contributes to the difficulties of conceptualizing and implementing threaded systems.

It should be "Objects use threads",
not "Threads use objects".

An object oriented program/system is an exchange of messages between objects resulting in side-effects useful towards the solution of the problem at hand. Thread objects (2b) are only an implementation mechanism, a way of calling a function; they do not help to manage complexity. Instead, thread object interactions add complexity and intrude on the main logic of the program.
In the scenario of single-threaded program development, come to think of it, the programmer plays the role of the thread object during coding- deciding which functions to call and in what order. Sadly though, this does not scale - which is why multithreaded programs are so challengingly complex.
Why have thread objects at all in programs? Threads are, very much, operating system entities (2c) . If we have the thread object in the program, it is only natural that they have methods similar to the real-world (albeit on the OS-side) entities that they model, with some high-level constructs thrown in for good measure. If we have thread objects, the complexity of threading cannot be hidden from the programmer.
For operating systems, threads are expensive to manage. The objection to live objects that is raised all too often is: "What! If I have a 1000 live objects, it would mean 1000 threads too? You must be out of your mind..." This mental block must be removed. The programming language back-end (the popular idiom of the runtime) can possibly be adapted to meet the external implementation constraints.
Hide the complexity of threads from the programmer, effectively.
Ideally, after the definition of live objects, one would be able to program like a classroom interaction like this:
void main()
TeacherLiveObject theProf;
StudentLiveObject theStudents[30];
That would be it?

A LOOP (Live Object Oriented Programming) language is the need of the hour. Such a language would help people to take their minds off low-level threading details and focus on more domain-relevant issues at hand, enabling the construction of more useful programs.

- Thomas Jay Cubb

External links
1 The Free Lunch Is Over
2 Software and the Concurrency Revolution
3 Discussion on Active Objects In Python
4 IBM Research- The X10 Programming Language - A LOOP language?
5 Polyphonic C# in Cw - Another LOOP language?

Tuesday, December 06, 2005

Cline's Commandments

* A class Fred's assignment operator should return *this as a Fred& (allows
chaining of assignments)
* A class with any virtual[20] functions ought to have a virtual
* A class with any of {destructor, assignment operator, copy constructor}
generally needs all 3
* A class Fred's copy constructor and assignment operator should have const in
the parameter: respectively Fred::Fred(const Fred&) and
Fred& Fred::operator= (const Fred&)
* When initializing an object's member objects in the constructor, always use
initialization lists rather than assignment. The performance difference for
user-defined classes can be substantial (3x!)
* Assignment operators should make sure that self assignment[12.1] does
nothing, otherwise you may have a disaster[12.2]. In some cases, this may
require you to add an explicit test to your assignment operators[12.3].
* In classes that define both += and +, a += b and a = a + b should generally
do the same thing; ditto for the other identities of built-in types (e.g.,
a += 1 and ++a; p[i] and *(p+i); etc). This can be enforced by writing the
binary operations using the op= forms. E.g.,

Fred operator+ (const Fred& a, const Fred& b)
Fred ans = a;
ans += b;
return ans;

This way the "constructive" binary operators don't even need to be
friends[14]. But it is sometimes possible to more efficiently implement common
operations (e.g., if class Fred is actually String, and += has to
reallocate/copy string memory, it may be better to know the eventual length
from the beginning).

Sunday, December 04, 2005

ISO C++ updates

A static const integer member is the one instance in C++ in which a class member can be initialized explicitly within the class definition, thereby making the value available to other constructs within the class, such as the dimension size of m_buffer. Without this construct, the use of an enumerator as a symbolic constant was a common workaround.

There is now covariant return type support in the definition of virtual functions. This is great news for those who write class hierarchies.

There is support for the implicit return of 0 within main.