Intel® oneAPI Threading Building Blocks
Ask questions and share information about adding parallelism to your applications when using this threading library.

lazy initialization

05522541
Beginner
921 Views
Hello all,
I'm trying to use lazy initialization from Design Patterns manual. This the code example (p. 36):
[cpp]template
class lazy {
    tbb::atomic value; 
   Mutex* mut;
public:
   lazy() : value(NULL) {} 
   ~lazy() {delete value;}
   T& get() {
       if( !value ) {
           T* tmp = new T();
            if( value.compare_and_swap(tmp,NULL)!=NULL )
                                                // Another thread installed the value, so throw away mine.
            delete tmp;
        }
         return value;
    }
};[/cpp]
First, Mutex* mut seems to be redundant here. Second, instantiation of lazy x, for example does not compiles ing++ (Ubuntu 4.4.3-4ubuntu5) 4.4.3 with:

[plain]main.cpp: In constructor lazy::lazy() [with T = int]:
main.cpp:30:   instantiated from here
main.cpp:10: error: no matching function for call to tbb::atomic::atomic(NULL)
...../tbb/atomic.h:314: note: candidates are: tbb::atomic::atomic(const tbb::atomic&)
...../tbb/atomic.h:314: note:                 tbb::atomic::atomic()[/plain]

Thanks for help

EDITED:

Found that this compiles:
[cpp]template
class lazy {
        tbb::atomic value;
public:
        lazy(){value=NULL;}

        ~lazy() {delete value;}

        T& get() {
                if( !value ) {
                        T* tmp = new T();
                        if( value.compare_and_swap(tmp,NULL)!=NULL )
                                // Another thread installed the value, so throw away mine.
                                delete tmp;
                }
                return *value.operator->();
        }
};[/cpp]
Changes on lines 5 and 16.
Please confirm if this is correct usage.
0 Kudos
13 Replies
RafSchietekat
Valued Contributor III
921 Views
The mutex was left over from the blocking variant and is indeed not needed here. Strangely, the non-blocking version does remove an asterisk from the return statement where it shouldn't, so how about just restoring "return *value;" instead (I didn't check, though)? Using the NULL argument for atomic initialisation seems like a rookie mistake (see "Why atomic Has No Constructors" in the Tutorial), but the atomic can still be in the initialisation list instead ("lazy():value() {}"), which is generally preferable.
0 Kudos
Alexey-Kukanov
Employee
921 Views
Thanks for letting us know about the issue.
Yes, such a way to initialize an atomic variable is correct. Since atomic deliberately misses any constructor (in order to be applicable in some sophisticated scenarios), it cannot be assigned a value via the initialization list.
An equivalent code is lazy() : value() {}. In this case, the C++ standard requires value to be zero-initialized.
0 Kudos
05522541
Beginner
921 Views
@Alexey Kukanov
No problem. Just for the sake of knowledge, why is that the standard requires value to be zero-initialized? Are some static variables involved under the hood?
Regards,
0 Kudos
RafSchietekat
Valued Contributor III
921 Views

Sorry, I wrote default-initialisation, but it's value-initialisation, which then through recursion implies zero-initialisation on the data member that represents the atomic's value. Any presence of static member variables would be irrelevant here.

0 Kudos
ARCH_R_Intel
Employee
921 Views
Thanks to all for pointing out the mistakes in the examples. (All are my errors - the atomic(NULL) was particularly bad since I wrote the spec for atomic!). Attached is a revision with corrections for review.
0 Kudos
RafSchietekat
Valued Contributor III
921 Views
How about making"lazy" noncopyable?
0 Kudos
jimdempseyatthecove
Honored Contributor III
921 Views

This would be more appropriately called a deferred allocation.

Also, consider this change to your code:

T&get(){
if(!value){
if(value.compare_and_swap(1,NULL)==NULL) {
value
=newT(); }
}
while((intptr_t)value == 1)
_mm_pause();
return*value.operator->();
}

The above is untested.

Jim Dempsey

0 Kudos
jimdempseyatthecove
Honored Contributor III
921 Views
Now to convert to a "lazy" new, assuming that the ctor of T is expensive. Assume you want to create a lot of these T objects and you do not wish to wait until all these objects are created before you start to use them (some of them).

The lazyobjectcan place the reference or pointerto itself into a concurrent vector (call it a lazy ctor queue)and if first fill initiate an asynchronous task to empty and 'new' the items in the lazy ctor queue. As this asynchronous task runs, other tasks can manipulate the objects that have been constructed. The lazy new task can also monitor the number of remaining new items, and if above a threshold, it can parallel_invoke to increase the number of threads performing the lazy news.

Jim Dempsey
0 Kudos
05522541
Beginner
921 Views
Thanks, but in this case I need only one.
0 Kudos
05522541
Beginner
921 Views
Hello all,
I think there still may be a problem if the object is destroyed. Then the lazy initializer won't know about it because its pointer has not been nullified, and thus will return pointer to destroyed object.
I think this bug currently ruins my code, but I could not think of any simple example to paste here.
Daniel
0 Kudos
RafSchietekat
Valued Contributor III
921 Views
"I think there still may be a problem if the object is destroyed."
That's no different from deleting a referent behind a smart pointer's back: don't do that.
0 Kudos
05522541
Beginner
921 Views
@Raf Schietekat
But eventually, every object will be destroyed, and it may be that another object will survive longer than that managed by lazy initializer and the former might ask for a pointer to the latter.
0 Kudos
jimdempseyatthecove
Honored Contributor III
921 Views
From your comments a section of your code code calls a ctor that evokes a lazy initializer, then your section of codeis going to use the object immediately following the ctor.

However, the functional intent of a lazy initializer would be to permit the ctor(s) to run concurrently with the section of code which issued the lazy initializer and after starting the lazy initializer, your code following the ctor would continue torun doing some useful work up until it needed to use the object(s) being lazy initialized. And at which point would block if initialization not (or until)complete. Should this interviening code decide to exit prior to completion of the lazy initialization, you have a rightful concern that holder of the pointer/reference will "evaporate" prior to completion of the lazy initialization. It is your responsibility to write your dtor such that this will not happen.

Jim Dempsey
0 Kudos
Reply