Thread hangs when no delay is used

When I remove delay(1) in the code below then last_frame will always be equal to frameCount and there is no movement.

Why is that?

PVector v = new PVector();

void setup() {
  thread("whyOwhy");
}

void whyOwhy() {
  
  int last_frame = 0;  
  
  while (true) {
    if (last_frame != frameCount) {
      v.x += 1;
      last_frame = frameCount;
    }
    delay(1);
  }
}

void draw() {
  background(0);
  fill(255);
  v.y += 0.1;
  fill(255);
  ellipse(v.x, v.y, 10, 10);
}

One reason may be that frameCount isnā€™t marked volatile so the JVM is quite entitled to read and store the value before entering your while loop.

For more Thread stuff, you can read this whole post: :alien:

2 Likes

Well, to sum up what Happens thereā€¦ :

if (frameCount != lastFrame) {
  lastFrame = frameCount;
}

So, if fC = 23 and LF = 23, then nothing happens, But if fC = 24 and LF = 23, then LF is set to 24. So if you Print LF After that Statement it will always be equal to fC. Because fC is set at the start of a new Frame. Then itā€™s checked if theyā€˜re equal and so Lc will end up as equal to fC after the statement. But before it should be different by 1, unless itā€˜s only set every n Frame. Then the difference is n.

I think @neilcsmith is right and frameCount is cached before the while loop by the JVM. Compiler is free to do this, because there is no synchronization in your code and the compiler assumes there will be no changes from different threads. Delay causes the thread to sleep which probably - I am guessing - creates a memory barrier and the values changed in other threads get updated in your whyOwhy thread as well.

Another problem is that v.x is updated in the background thread, but the main thread might not see the changes.

Both of these problems happen because the access to these variables is not synchronized in your sketch.

To make programs behave in a predictable way, you must synchronize all access to data which is touched from multiple threads (except read-only data). A consequence of this is you canā€™t safely read or write any built-in variables from threads, because PApplet modifies them without synchronization and you canā€™t fix it from within your sketch. You need to create your own variables which you then make sure are properly synchronized.

Thatā€™s definitely one possibility, although if so is going to be implementation / CPU specific - pretty sure itā€™s not specified to create one.

Of course, another option is that theyā€™re both broken! Just one isnā€™t given time to show up. Aggressive compilation depends on threshold numbers (1000s) of times through a loop. A hard loop will hit that quickly. Add a sleep in and the compile threshold just might not be being reached in the observed time.

One of the biggest problems I see with multi-threaded examples here that donā€™t use synchronisation or volatiles is the assumption that because things work for a while theyā€™ll carry on working.

Good point! If the only way of observing whether the frameCount value triggers the if clause is this then either or both could be the problem.

1 Like

IMO, thatā€™s what happens: delay() forces an update between the local Threadā€™s cache & the main memory. :thread:

So thereā€™s no need for any further types of synchronization (even volatile fields) if we use delay(), in case we just need all local fields to be updated to the main memory. :snail:

A tight loop w/o any delay(), besides hammering the CPU super hot nonstop, can make all changes to a field (or an arrayā€™s index) to stay stuck in the current Threadā€™s local cache memory. :hot_face:

This is just not true. Please stop giving people stupidly erroneous advice. There is a Java memory model spec. Follow it or donā€™t be surprised if at some point something might break.

For further reading, see the second and third paragraph of 17.3 here, which explicitly covers your point - Chapter 17. Threads and Locks

ā€¦ the compiler does not have to flush writes cached in registers out to shared memory before a call to Thread.sleep or Thread.yield, nor does the compiler have to reload values cached in registers after a call to Thread.sleep or Thread.yield.

The main thing is: ā€œdoes not have toā€.
It means it isnā€™t obliged to, but still it has permission to do so!

implementors are free to go beyond the minimum specs, as long as the languageā€™s spec doesnā€™t prohibit it.

And so far, Thread.sleep() (delay() in Processing) hasnā€™t failed to flush local cached fields to main memory for every single case where threads are used in Processing.

You canā€™t make a reliable concurrent program by testing it and saying that it works so the code is right. You have to be able to defend that everything is properly synchronized and accounted for.

Thatā€™s the hard part of concurrency in Java: you canā€™t fiddle around until it seems to work, you have to know that your code is right.

Otherwise you get strange bugs which happen only sometimes, usually during exhibitions or performances. Good luck finding the source of the problem then :sweat_smile:

1 Like

You have absolutely no basis to make that statement! You just havenā€™t observed it yet. And even if you have something that works despite the spec you canā€™t guarantee that running it on a different machine or OS or next point release of the JVM wonā€™t break it. Iā€™ve been in enough conference talks and discussions with people involved with HotSpot not to trust a word you say on it!

Jakubā€™s right, these are often things that donā€™t show up until long running in performances and exhibitions. Been there, got the T-shirt! :smile: PraxisLIVE actually started out because of this, not as a live programming environment, but as an actor-based architecture I could build projects with using a universal lock-free message passing system - all cross-thread communication routed through one place. Processing could really benefit from a simplified but similar mechanism - threading just confuses beginners (and non-beginners :stuck_out_tongue_winking_eye: )

Thanks for all the help.

I have it different know.

I made

public void set_time_out(int time, Runnable callback) {

To have things on another thread that donā€™t draw in the animation thread etc.

And I have the non async for_n_frames:

To make a vector move in a square for example I can use:

int frames = 60;
for_n_frames(frames, ()-> {
            v.x++;
        }, 
        ()-> {
            for_n_frames(frames, ()-> {
                v.y++;
            }, 
            () -> {
                for_n_frames(frames, ()-> {
                    v.x--;
                }, 
                ()-> {
                    for_n_frames(frames, ()-> {
                        v.y--;
                    }, 
                    callback); 
                });
            });
        });