15

I am creating an application which displays the market data and uses it in some other forms too. I store market data in a map say std::map<tickerId, StockData>. Let me give one used case of how this map can be used.

  1. network sends a data packet encapsulating the stock Data at time t. updatePrice(tickerId, latestPrice)
  2. update the stock data in the map. Now, multiple threads can access/update the data. So the map has to be locked for thread-safe operations. Here is the first question, do I need to lock the underlying data too for updates?
  3. There are multiple uses of the new stock data, say, there is a price update on IBM, then I need to update the value of IBM in my portfolio. As well as display the new data on a screen. And there can be several other simultaneous uses.updatePosition(tickerId, price) and updateStockScreen(tickerId, price). Also, separting Gui updates from position update is important as GUI is not the main strength of the application.
  4. I am just troubled about how to implement this type of design. I read about Model/View Design in QT to display data but if View thread reads from the same map, it has to be locked. This leads to an slow/inefficient design. Every time view reads from the model, the model needs to be locked. Is this preffered in real-time GUIs?
  5. To summarize, I have stored a lot of different objects as maps. And objects are updated in realtime. I need to update them and then use them at various locations. It would be great if someone can give me a small example on how to implement such designs.

Some references to useful books are appreciated too.

I am new and trying to achieve too much with my little knowledge so forgive me if I have asked stupid/ill-formed questions.

Thanks Shiv

shiv chawla
  • 591
  • 1
  • 8
  • 20
  • I just answered a similar question to this one here: http://stackoverflow.com/questions/9476045/can-two-threads-read-from-the-same-qlist-at-the-same-time/9476153#9476153, although your question is far more well written, so thank you for that! Agreeing with @HostileFork, I feel using signals is the best way to communicate the data. I was wondering though, what kind of view are you able to run in a thread? Is it a non-gui view? – jdi Feb 28 '12 at 21:04

2 Answers2

16

It sounds conceptually like you want the model on one thread and the view on another, which I looked into at one point.

If so...and your model is read-only through the view widget then yes, you have to lock. I'd argue that doing so undermines the elegance of the "decoupling" provided by the model/view separation. But it could be made to work.

However...if your model is read-write through the view it's not possible to do correctly at all because of the queued nature of the notification slots. Here's an archive of a mailing list conversation I had on the qt-interest mailing list on the topic:

http://blog.hostilefork.com/qt-model-view-different-threads/

"The short version is that I don't think it's feasible for a Model to
be modified on a non-GUI thread...regardless of whether the model's
data has been protected with read/write locks. If what I'm gathering
is correct, then Qt should probably have an assert that a model and
its view have the same thread affinity (it doesn't seem to do that now)"

A subsequent unit test by a KDE developer verified this.

I feel the best way to work around this is to keep the model and the view on the same thread, and only modify the model in the GUI thread. So if the worker thread wishes to change it then it should use a signal.

Whether the worker needs to keep their own copy of the data from which the model was created (or if it needs to get notifications to keep that up to date when the user changes the model through the view) depends on your app. If I understand you correctly, it sounds like like you could probably get away with just ferrying the updates through signal/slots and forgetting them on the worker...

  • Well, model is updated by a separate thread altogether and I have no control over it. I think I need to come up with some kind of pipeline design model to implement what I need. – shiv chawla Feb 28 '12 at 21:30
  • 1
    @ShivChawla Needing to react to events coming from a separate thread does not mean you must also have `QAbstractItemModel`-derived classes instantiated in that thread. Models should only live on the GUI thread. It's a little tricky to get, but please give a thorough reading/re-reading of the links above to understand why this is the case... – HostileFork says dont trust SE Feb 28 '12 at 21:58
  • @HostileFork Actually, you are right. If the incoming change can somehow be transferred to Model/View thread, then it will work. I need to learn more about how this signal-slot works. But consider this situation, worker thread signals the model thread very rapidly. In turn, whenever view asks the data from the model, it might have to wait because model is being updated by the signal. Isn't this a similar situation or have I understood it incorrectly? What's special with the signal? – shiv chawla Feb 28 '12 at 22:22
  • 1
    @ShivChawla: Be sure to read up on "thread affinity" of a `QObject` and its effects on signal/slots. You might want your worker thread to gather a block of updates at once, and throttle the rate at which it sends a bulk update signal to the GUI. That might reduce the time spent in overhead that the GUI thread takes when it executes the slot triggered by the worker. It may help to examine my SignalThrottler class from Thinker-Qt: http://gitorious.org/thinker-qt/thinker-qt/blobs/master/src/signalthrottler.cpp – HostileFork says dont trust SE Feb 29 '12 at 14:04
  • @HostileFork Thanks for your replies. I was pondering over you replies and now I understand the concept of thread affinities and how mode-view thing works. I think I do. So here is a follow up question. If I signal the updates to the model, it's all good and great. But say I need to use the model in worker thread too, does that call for separate/duplication of model in worker object. Think about an application where display is just a secondary task. – shiv chawla Mar 07 '12 at 22:06
  • @shivchawla Follow-up questions on StackOverflow should be new questions and not comments on an old one. :) You can always link to a previous question if you like... – HostileFork says dont trust SE Mar 08 '12 at 22:03
2

I learned another potential problem today, the hard way, even if the model was read only. I use another thread to modify the data in the model (actually, my program is over 20 threads, and they all play nice), which then a Qt timer updates. This works very well, but there is a problem I fell into, which is:

You cannot lock between rowCount/columnCount and data().

Qt works sequentially, meaning that in human language, it'll ask "how big are you", and then ask "what data do you have at this position", and these are prone to break.

Consider:

int FilesQueue::rowCount(const QModelIndex &/*parent*/) const
{
    std::lock_guard<decltype(mainQueueMutex)> lg(mainQueueMutex);
    return filesQueue.size();
}
QVariant FilesQueue::data(const QModelIndex &index, int role) const
{
    std::lock_guard<decltype(mainQueueMutex)> lg(mainQueueMutex);
    if ( role == Qt::DisplayRole) {
        return filesQueue[index.row()]->getFilename();
    }
}

Qt will do the calls like this:

//...
obj->rowCount();
obj->data(...);
//...

And I had assertion failure all over the place because simply, between rowCount() and data(), there was a thread that was changing the size of the data! It broke the program. So this was happening:

//...
obj->rowCount();
//another thread: filesQueue.erase(...)
obj->data(...);
//...

My solution to the problem is to verify the size, again, in the data() method:

QVariant FilesQueue::data(const QModelIndex &index, int role) const
{
    std::lock_guard<decltype(mainQueueMutex)> lg(mainQueueMutex);
    //solution is here:
    if(static_cast<int>(filesQueue.size()) <= index.row())
        return QVariant();
    if ( role == Qt::DisplayRole) {
        return filesQueue[index.row()]->getFilename();
    }
}

and there goes 3 hours of my life I'll never get back :-)

The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189