in reply to Re^6: bidirectional pipe freezing
in thread bidirectional pipe freezing

PROVIDED that I'm not tempted to use Tk functions in my working thread is it GUARANTEED that my code is safe?

Safe from what? I'll assume you mean: safe from problems arising as a result of using Tk in a threaded program.

Think of it this way. Every program that uses Tk is a threaded application. It may only have one thread, but it is still threaded.

Indeed -- certainly in Windows, and arguably in other OSs too, though I am very vague of their internals -- the fundamental unit of processing is the thread. It is threads that are scheduled by the scheduler, not processes.

So, if you take a single-threaded Tk app and start another thread that does nothing but sleep, will that bring the house down? I think you;d have to agree, probably not.

So what if instead of just sleeping, that thread loops around? Still probably safe. So what if it does lots of things: reads and write from disk; communicates via sockets; allocates big chunks of ram; and does billions of calculations; will that cause Tk to fall over? Again, my given understanding of iThreads is that because no memory is shared between threads unless you explicitly request for it to be so, there should be no interactions between threads that do not explicitly communicate.

Temper that "no interactions" with the knowledge that some things are process global. Eg. the screen, keyboard etc. But iThreads design is such that it will do its best to keep your threads separate except where you explicitly choose for them to interact.

Of course, if the threads were entirely separate, they might just as well be separate processes. Well almost. So to be really useful, it usually means that they need to communicate some way. And Thread::Queue is, to the very best of my knowledge, a safe way for threads to communicate.

Would I use tk and threads to control a nuclear power station, airliner or baby monitor. No. But for most non-life-critical applications, I'd be perfectly happy to.

But, guaranteed? No.


With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.

The start of some sanity?

Replies are listed 'Best First'.
Re^8: bidirectional pipe freezing
by chessgui (Scribe) on Dec 21, 2011 at 23:31 UTC
    Ok. I think I've got your point.

    I still have a lesser ('cosmetic') problem though. Namely that I don't really need a queue. The main task of the worker thread is to monitor the internal state of the engine which is in fact a hash.

    The main window's handler reads this hash at regular intervals and represents it on the screen. The only commands I issue is practically 'start' and 'stop' which don't need to be queued.

    So I'm wondering in WHAT WAY is a queue safer than a shared variable?

    like:
    use threads; use threads::shared; my(%state):shared;
      So I'm wondering in WHAT WAY is a queue safer than a shared variable?

      Ostensibly, in no way. Given that a queue is just a shared array under the covers.

      But, accessing shared variables requires locking. With Thread::Queue that locking is taken care of for you. It has been used many times by many people and thus proven correct.

      There is certainly nothing stopping you from using a shared hash and performing the locking yourself. But then you have the responsibility to test that you're doing it correctly. Whilst not exactly rocket science, it is still possible to get it wrong.

      From a practical stand point -- based upon the little you've said about it -- it seems that at some regular interval, your GUI (re-)draws the on-screen representation of the hash by iterating over its keys & values.

      Assuming that's how you are doing things, there are several possibilities:

      • You might simply re-draw the entire picture anew each time.

        Simple. But what if the hash didn't change since the last time you did it. It becomes make-work.

      • You might improve on that by (say), having the worker set some flag in the hash to indicate that it changed something else.

        When there is nothing changed you save the work.

        But when something has changed you need to re-build from scratch for what might be a simple change. And you need to iterate the entire hash to do so.

      • You might improve upon that by taking a local copy of the hash in the GUI.

        Now when the worker sets the flag, you can compare the shared and local copies and only update the bit or bits that changed.

        You still have to iterate the entire hash to find it. But now you also have to iterate the local copy. And you have to update the local copy.

      And don't forget that each time you are iterating the hash in the GUI, you will have to lock it to prevent the worker from changing it whilst you are in the process of iterating it. And that means that the worker either has to stop doing what it is meant to be doing and wait for you to unlock the hash before it can record changes -- thus potentially missing something. Or it has to record changes that occur whilst the shared hash is locked, locally, so that it can remain responsive to whatever is causing those changes, and still update the hash properly once it becomes unlocked.

      Now I have no idea how time critical your application is, so maybe none of that is a concern for you.

      But imagine how much simpler it becomes if instead of the worker locking & updating a shared hash and then unlocking it again; and the GUI constantly locking the hash and iterating over it to discover what changed, if the worker simply told the GUI what changed when it changed. And the GUI does nothing until it receives that notification.

      And the easy way to do that is a queue.

      When something changes, instead of setting the key/value into the shared hash, it simply enqueues a message of the form: "key:value".

      And when the GUI makes it regular check, it just checks if there is anything pending in the queue. If not, it does nothing. If there is, it pulls the message and either updates the screen directly or updates a local hash and then invokes a redraw.

      No waiting; no locking (except under the covers of Thread::Queue); no iterating a hash to discover changes; no make-work. When something changes, you get told not just that something has changed, but what.

      It takes far less time/cpu to push and pop a single value to a shared array than it does to iterate a shared hash looking for changes, so there is less contention also.


      With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.

      The start of some sanity?