Results 1 to 14 of 14

Thread: QAtomicPointer lazy initilization

  1. #1
    Join Date
    Nov 2015
    Posts
    17
    Thanks
    9
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default QAtomicPointer lazy initilization

    Hello,

    I have a multithreaded application and I want to implement lazy initialization via QAtomicPointer. I read the docs and tried to implement it, but I need help.

    My initilization without QAtomicPointer looks like the following snippet:
    Qt Code:
    1. //member of class
    2. QHash<QString, QMap<int,Data*>* > hash;
    3.  
    4. //...
    5.  
    6. //method
    7. QMap<int,Data*>* Manager::getDataMap(const QString& key)
    8. {
    9. mutex.lock();
    10.  
    11. if(!hash.contains(key)){
    12. getDataMap_private(key);//private method does lazy initialization
    13. }
    14.  
    15. mutex.unlock();
    16. return hash.value(key);
    17. }
    To copy to clipboard, switch view to plain text mode 

    Can you show, or explain, how I could achieve the wanted behaviour by using QAtomicPointer? fetchAndStoreOrdered is the right method, isn't it?
    Thank you!

  2. #2
    Join Date
    Jan 2006
    Location
    Graz, Austria
    Posts
    8,416
    Thanks
    37
    Thanked 1,544 Times in 1,494 Posts
    Qt products
    Qt3 Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: QAtomicPointer lazy initilization

    You mean having QAtomicPointer as the value in the hash and creating all possible entries in "hash" but with empty pointers?

    Cheers,
    _

  3. #3
    Join Date
    Nov 2015
    Posts
    17
    Thanks
    9
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: QAtomicPointer lazy initilization

    Thank you for your resp.
    no, i mean not creating all values. i mean creating the qmap for the key. so the qmap has to be wrapped by qatomicpointer?

  4. #4
    Join Date
    Oct 2009
    Posts
    483
    Thanked 97 Times in 94 Posts
    Qt products
    Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: QAtomicPointer lazy initilization

    QAtomicPointer lets you atomically change the value of a pointer. This is far different from what you want: atomically look up a QHash and, if the key is not present, allocate and add a new binding. There is no way to make such a complex operation atomic without a mutex. That is what they are for.

  5. #5
    Join Date
    Nov 2015
    Posts
    17
    Thanks
    9
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: QAtomicPointer lazy initilization

    ok, thank you for the conclusion.so qatomicpointer is not usable in my case. do you have a hint, how i could solve my problem? performance is very important, and several threads questions the map for the same key at the same time.

  6. #6
    Join Date
    Jan 2006
    Location
    Graz, Austria
    Posts
    8,416
    Thanks
    37
    Thanked 1,544 Times in 1,494 Posts
    Qt products
    Qt3 Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: QAtomicPointer lazy initilization

    Quote Originally Posted by QtCrawler View Post
    no, i mean not creating all values. i mean creating the qmap for the key.
    Yes, that is what I meant.
    You would start with the hash fully "populated" but each entry would be an empty QAtomicPointer, no?

    If you need to dynamically add to "hash", then you need to protect access to it with a mutex, as yeye_olive explained.

    Cheers,
    _

  7. #7
    Join Date
    Oct 2009
    Posts
    483
    Thanked 97 Times in 94 Posts
    Qt products
    Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: QAtomicPointer lazy initilization

    Quote Originally Posted by QtCrawler View Post
    ok, thank you for the conclusion.so qatomicpointer is not usable in my case. do you have a hint, how i could solve my problem? performance is very important, and several threads questions the map for the same key at the same time.
    There are two separate issues here.

    About the structure of the hash map: if the set of possible keys is small enough, I urge you to follow anda_skoa's suggestion and pre-populate the hash map so that the structure is immutable during concurrent accesses. If you cannot do that, you could look for libraries that specifically address this problem. See http://stackoverflow.com/questions/1...currenthashmap for instance.

    About allocating the values stored in the map: the values in your map are of type QMap<int,Data*>*. Adding a new binding is a long operation because you need to construct a new QMap<int,Data*>; that cannot be done atomically without locking. I suggest two possible solutions:
    • turn the QMap<int, Data*>* into an atomic pointer and attach a mutex to it (this means changing the value type of the hash map to a pair). You can then use double-checked locking when reading the pointer; your threads will only lock when the binding is added, not on subsequent accesses. Beware, turning the value type of the hash map into a pair means that allocating this pair is not atomic, which may prevent you from using concurrent hash map implementations (see "About the structure of the hash map" above).
    • turn the QMap<int, Data*>* into an atomic pointer and, when reading it as nullptr, construct a QMap<int, Data*> and use a test-and-set operation to try and set the pointer; if some other thread has already set it in the meantime, just abandon and destroy the QMap<int, Data*> you have just constructed. Use this solution if you can tolerate, in rare cases, that a QMap<int, Data*> be uselessly constructed and destroyed.


    Oh, and QMap<int, Data*> itself does not sound too good as far as concurrent accesses are concerned. I hope you do not plan to concurrently update *those QMaps* too...

  8. #8
    Join Date
    Nov 2015
    Posts
    17
    Thanks
    9
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: QAtomicPointer lazy initilization

    Thank you for your explanation. Now I'm a bit more clued up on QAtomicPointer.

    Oh, and QMap<int, Data*> itself does not sound too good as far as concurrent accesses are concerned. I hope you do not plan to concurrently update *those QMaps* too...
    Can you explain why QMap<int,Data*> is not too good for this purpose? Because of performance?


    Added after 53 minutes:


    It's no option for me to pre-populate the QHash, cause the keys are not known at init-time.
    I would like to implement a pure Qt - Solution, instead of using the 3rd part libs.
    Last edited by QtCrawler; 19th November 2015 at 15:31.

  9. #9
    Join Date
    Oct 2009
    Posts
    483
    Thanked 97 Times in 94 Posts
    Qt products
    Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: QAtomicPointer lazy initilization

    Quote Originally Posted by QtCrawler View Post
    Can you explain why QMap<int,Data*> is not too good for this purpose? Because of performance?
    Because you have exactly the same problem with these QMaps as with the QHash: you must not allow concurrent accesses to a QMap if one of them mutates the structure.

    Similarly, I do not know what Data is, but if you plan to allow concurrent accesses to a Data instance, you will probably need synchronization.

    Quote Originally Posted by QtCrawler View Post
    It's no option for me to pre-populate the QHash, cause the keys are not known at init-time.
    I would like to implement a pure Qt - Solution, instead of using the 3rd part libs.
    Then you have to lock the QHash with a mutex -- or a double-checked lock, which could improve performance -- on every access, whether it is a read or a write. If that it not enough, then I really suggest looking for specialized libraries. The problem you are trying to solve is notoriously hard.

  10. The following user says thank you to yeye_olive for this useful post:

    QtCrawler (20th November 2015)

  11. #10
    Join Date
    Nov 2015
    Posts
    17
    Thanks
    9
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: QAtomicPointer lazy initilization

    yeye_olive, thank you for your help.

    Ok, if you say double-checked locking. What do you say about following pattern?
    I think that would be sufficient, wouldn't it?
    Or do you see week points?

    Qt Code:
    1. //member of class
    2. QHash<QString, QMap<int,Data*>* > hash;
    3.  
    4. //...
    5.  
    6. //method
    7. QMap<int,Data*>* Manager::getDataMap(const QString& key)
    8. {
    9. QMap<int,Data*>* map = hash.value(key,0);
    10.  
    11. if(!map){
    12. mutex.lock(); //only the first thread, which enters will create the QMap
    13. map = hash.value(key,0);
    14. if(!map){
    15. getDataMap_private(key);//private method does lazy initialization
    16. }
    17. mutex.unlock();
    18. }
    19.  
    20. return hash.value(key);
    21.  
    22. }
    To copy to clipboard, switch view to plain text mode 

  12. #11
    Join Date
    Oct 2009
    Posts
    483
    Thanked 97 Times in 94 Posts
    Qt products
    Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: QAtomicPointer lazy initilization

    Your code has undefined behavior. For instance, line 9 inspects the structure of the QHash without synchronization, which means that a thread may be doing that while another is modifying the QHash at line 15. Your code only protects multiple writers from each other, but it does not protect readers from writers.

    By the way, the code snippet in your first post suffered from this problem too. Here is a fixed version:
    Qt Code:
    1. QMap<int,Data*>* Manager::getDataMap(const QString& key)
    2. {
    3. mutex.lock();
    4.  
    5. if(!hash.contains(key)){
    6. getDataMap_private(key);//private method does lazy initialization
    7. }
    8.  
    9. QMap<int,Data*>* result = hash.value(key); // We must be protected by the mutex when calling QHash::value()
    10. mutex.unlock();
    11. return result;
    12. }
    To copy to clipboard, switch view to plain text mode 
    However, you can rewrite that in a simpler way with a QMutexLocker:
    Qt Code:
    1. QMap<int,Data*>* Manager::getDataMap(const QString& key)
    2. {
    3. QMutexLocker locker(&mutex);
    4.  
    5. if(!hash.contains(key)){
    6. getDataMap_private(key);//private method does lazy initialization
    7. }
    8.  
    9. return hash.value(key); // Notice how the QMutexLocker protects this read
    10. }
    To copy to clipboard, switch view to plain text mode 
    Note that you can do even better by calling hash[key], which returns a reference to the value bound to key, adding it if not present (with a default NULL value). That way you only compute the hash once per access.

    I was wrong in my previous post about double-checked locking. It only applies when initializing an atomic value, e.g. if the QHash were pre-populated with null QAtomicPointer<QMap<int,Data*>> (see the previous posts in the thread). I cannot think of a better solution than using a mutex.

  13. #12
    Join Date
    Jan 2006
    Location
    Warsaw, Poland
    Posts
    33,360
    Thanks
    3
    Thanked 5,015 Times in 4,792 Posts
    Qt products
    Qt3 Qt4 Qt5 Qt/Embedded
    Platforms
    Unix/X11 Windows Android Maemo/MeeGo
    Wiki edits
    10

    Default Re: QAtomicPointer lazy initilization

    Quote Originally Posted by QtCrawler View Post
    yeye_olive, thank you for your help.

    Ok, if you say double-checked locking. What do you say about following pattern?
    I think that would be sufficient, wouldn't it?
    Or do you see week points?

    Qt Code:
    1. //member of class
    2. QHash<QString, QMap<int,Data*>* > hash;
    3.  
    4. //...
    5.  
    6. //method
    7. QMap<int,Data*>* Manager::getDataMap(const QString& key)
    8. {
    9. QMap<int,Data*>* map = hash.value(key,0);
    10.  
    11. if(!map){
    12. mutex.lock(); //only the first thread, which enters will create the QMap
    13. map = hash.value(key,0);
    14. if(!map){
    15. getDataMap_private(key);//private method does lazy initialization
    16. }
    17. mutex.unlock();
    18. }
    19.  
    20. return hash.value(key);
    21.  
    22. }
    To copy to clipboard, switch view to plain text mode 
    Google for "lock free data structures". Maybe you can find something that fits your needs. The standard containers are not thread-safe and if you use mutexes, you will lose performance.
    Your biological and technological distinctiveness will be added to our own. Resistance is futile.

    Please ask Qt related questions on the forum and not using private messages or visitor messages.


  14. #13
    Join Date
    Oct 2009
    Posts
    483
    Thanked 97 Times in 94 Posts
    Qt products
    Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: QAtomicPointer lazy initilization

    As already written several times in this thread, the right way to go is to use a specialized concurrent map. That being said, since the OP insists on using Qt containers, I can see two ways to hack around the limitations of QHash and improve the mutex-based solution.

    Improvement 1: compute the hash outside the critical section

    All retrieval operations (QHash::contains(), QHash::operator[], QHash::value()) must be performed in the critical section, which means that threads perform the (costly) hash computation in the critical section. This is suboptimal, because the hash computation does not touch the QHash's structure. A possible optimization consists in wrapping the QString key in an object that already knows its hash, and to construct this object outside the critical section. Proof of concept code (I haven't even tried to compile it):
    Qt Code:
    1. class HashedString {
    2. public:
    3. HashedString(QString s);
    4. uint hash() const {return m_hash;}
    5. bool operator==(const HashedString &other) const {return m_s == other.m_s;}
    6. private:
    7. QString m_s;
    8. uint m_hash;
    9. };
    10.  
    11. HashedString::HashedString(QString s) : m_s(s) {
    12. m_hash = qHash(s);
    13. }
    14.  
    15. inline uint qHash(const HashedString &key) {
    16. return key.hash(); // Super fast, because the hash is already known
    17. }
    18.  
    19. QHash<HashedString, QMap<int, Data *> *> hashmap;
    20.  
    21. QMutex mutex;
    22.  
    23. QMap<int, Data *> *Manager::getDataMap(const QString& key) {
    24. HashedString hashedKey(key); // Compute the hash outside the critical section
    25. QMutexLocker locker(&mutex);
    26. QMap<int, Data *> *&val = hashmap[hashedKey]; // Notice the '&', which is required to be able to update the value
    27. if (val == NULL)
    28. // New value; compute it
    29. val = newDataMap_private(key);
    30. return val;
    31. }
    To copy to clipboard, switch view to plain text mode 

    Improvement 2: allow concurrent read accesses

    If it is expected that many accesses will be made for the same key, then we can be optimistic and consider that most reads will return an existing binding. We can then use a QReadWriteLock instead of a mutex. Again, here is code I haven't tried to compile:

    Qt Code:
    1.  
    2. QMap<int, Data *> *Manager::getDataMap(const QString& key) {
    3. HashedString hashedKey(key); // Compute the hash outside the critical section
    4. lock.lockForRead(); // Let's be optimistic
    5. QMap<int, Data *> *val = hashmap.value(hashedKey); // Read-only access to the QHash
    6. if (val == NULL) {
    7. // New value; relock the map for writing
    8. lock.unlock();
    9. // Some other thread might have time to insert a value for the same key here
    10. lock.lockForWrite();
    11. QMap<int, Data *> *&val2 = hashmap[hashedKey]; // Notice the '&', which is required to be able to update the value
    12. if (val2 == NULL)
    13. // No one has inserted the value
    14. val2 = newDataMap_private(key);
    15. val = val2;
    16. }
    17. lock.unlock();
    18. return val;
    19. }
    To copy to clipboard, switch view to plain text mode 

  15. #14
    Join Date
    Nov 2015
    Posts
    17
    Thanks
    9
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: [solved]QAtomicPointer lazy initilization

    Thank you for your help. I think this thread can be marked as solved.

Similar Threads

  1. initilization of QScopedPointer
    By weaver4 in forum Newbie
    Replies: 3
    Last Post: 8th April 2014, 21:11
  2. Lazy population of QAbstractTableModel
    By superpacko in forum Qt Programming
    Replies: 1
    Last Post: 26th October 2011, 05:04
  3. Replies: 4
    Last Post: 7th May 2010, 04:13
  4. QStandardItemModel Lazy Population
    By AaronMK in forum Qt Programming
    Replies: 0
    Last Post: 15th September 2009, 09:05
  5. Lazy population of QAbstractModel
    By bastien in forum Qt Programming
    Replies: 1
    Last Post: 16th January 2009, 21:25

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Digia, Qt and their respective logos are trademarks of Digia Plc in Finland and/or other countries worldwide.