Threads + Concurrency & Weird Issue

Started by
9 comments, last by The_Neverending_Loop 11 years, 3 months ago
Hello all, I been having a weird issue with my code that I can't seem to get working so I thought I'd try asking the community.

Basically I have created a class called TimerManaged which extends a thread, this class has a loop that runs in its run() method that checks any TimedEvent added to it. the TimedEvent class is abstract and is overwritten by whatever class needs it, it does however have a float timeReference variable that references its start time with respect to the global time of TimerManaged.

The TimerManaged checks all these TimedEvents by calling their monitor method that checks to see if anything was suppose to happen at time T, if it was then execute that event.

Now the issue I'm having is that the events only get executed in the first run through, since alot of these events repeat at a later time the TimedEvent class also has a reset method that essentially resets its local time back to zero with respect to the global time. But the reset doesnt seem to work properly UNLESS i add a System.out.println("" + TimedEvent.LocalTime) call in either the TimedEvents.monitor() method, or inside the loop that is calling the TimedEvents monitor class. Things work perfectly when I have that system.out there, but it doesnt work other wise. I tried modifieing the LocalTime variable for the TimedEvents volatile, nothing was fixed. I also modified the add, remove, monitor for the TimedEvents and TimerManaged synchronized, but again this didn't fix anything.

I'm really pulling my hairs out with this issue so any help will be greatly appreciated. Thank you.
Advertisement
Could you provide some code?

Also it almost sounds as if you are creating a thread for each event? And using another thread to activate them? Threads aren't some thing you should create a lot of. They will mostly just give you trouble. If you want to add timed events you should either only have one thread doing it, or even better, add a priority queue in your game loop and perform and remove/reinsert the event at the top of the queue if its time has expired.

But any ways post your code, then I'll take a look at it tomorrow :)
Sorry bout the delay heres the 2 main classes

[source lang="java"]public class TimerManaged extends Thread
{
// TimerManaged can be either put into a do while loop, or run as a thread
Vector<TimedEvent> tes = new Vector<TimedEvent>();
float gTimeSRef; // in seconds
long gTimeTRef; // in ticks

float gTimeS;
long gTimeT;
TimedEventListener tel = null;

public TimerManaged()
{

}

public void setTimedEventListener(TimedEventListener _tel)
{
tel = _tel;
}

@Override public void run()
{
try
{
startGlobalTime();
while(true)
{
monitor();
}
}
catch(Exception e)
{

}
}

public void startGlobalTime()
{
gTimeTRef = System.nanoTime();
gTimeSRef = gTimeTRef/1000000000f;
}

public float ticksToSeconds(long ticks)
{
return ticks/1000000000f;
}

public long getGlobalTimeTicks()
{
return System.nanoTime() - gTimeTRef;
}

public float getGlobalTimeS()
{
return getGlobalTimeTicks()/1000000000f;
}

public synchronized void addTimedEvent(TimedEvent _te)
{
tes.add(_te);
}

public synchronized void removedTimedEvent(TimedEvent _te)
{
tes.remove(_te);
}

public synchronized void clearTimedEvents()
{
tes.clear();
}

public synchronized void monitor()
{
gTimeT = System.nanoTime() - gTimeTRef;
gTimeS = gTimeT/1000000000f;


for(int i=0; i < tes.size(); i++)
{
TimedEvent te = tes.get(i);

if(te.monitor(te.getLocalTime(gTimeS)))
{
// something happened
tel.interpretTimedMessage("", te, te.getLocalTime(gTimeS));
}
}
}
}[/source]

and

[source lang="java"]public abstract class TimedEvent
{
TimerManaged mngr;

volatile long lTimeTRef;
volatile float lTimeSRef;

public TimedEvent(TimerManaged _mngr)
{
mngr = _mngr; // all time events need a manager
}

public void setManager(TimerManaged _mngr)
{
mngr = _mngr;
}

public float getLocalTime(float totalTimePassed)
{
// System.out.println("TE[getLocalTime] " + (totalTimePassed - lTimeSRef));
return totalTimePassed - lTimeSRef;
}

public void start()
{
lTimeTRef = mngr.getGlobalTimeTicks();
lTimeSRef = mngr.ticksToSeconds(lTimeTRef);
// System.out.println("TE[Start] " + lTimeSRef);
}

public void reset()
{
lTimeTRef = mngr.getGlobalTimeTicks();
lTimeSRef = mngr.ticksToSeconds(lTimeTRef);
// System.out.println("TE[Reset] " + lTimeSRef);
}

public abstract boolean monitor(float tPassed);
}[/source]
What is the intended use here? Can you post a sample console program?

I tried modifieing the LocalTime variable for the TimedEvents volatile, nothing was fixed. I also modified the add, remove, monitor for the TimedEvents and TimerManaged synchronized, but again this didn't fix anything.
[/quote]
This is not a good way to go about writing multi-threaded code. You need to understand why something is made volatile, and why something is synchronised. If you don't know, then your code is probably broken and not thread safe - even if it appears to work.
@Rip-off its actually graphical application, and the issue is anytime I add console output for the TimedEvents localtime, the problem is fixed. However when I take it off it stops working.

Basically the intended use is you create a TimedEvent which is monitors by the TimerManaged class. The TimerManaged runs in its own thread and checks the TimedEvents assigned to it. The issue is that when I use TimedEvent.reset() to force that event to run again, it doesn't. I know synchornized is used for when you have two threads sharing the same information to ensure one completes before another one gets access to that data, and I know volatile is for atomic type operations to ensure that the data is the same across the threads.

Writing this response I start to think that maybe lTimeTRef = mngr.getGlobalTimeTicks(); is the issue since mngr.getGlobalTimeTicks isn't exactly a atomic operations. Ill try to change and see if that fixes anything, but any other suggestions are more then welcomed.

[...] and the issue is anytime I add console output for the TimedEvents localtime, the problem is fixed.


Most console output will be automatically synchronized. That generally does not fix the problem, but makes it appear more consistent (either consistently breaking or consistently working). The key word here is 'appear', running the program on any other computer or making minor code changes can lead to completely different results.
A lot of things can go wrong with multithreading. It requires a lot of effort and experience to write safe, thread-safe code. I strongly advise reading a lot about multithreading before writing the first line of code and only trying it out in test cases which are engineered to catch concurrency errors. Only after you have understood why all of your errors appeared and you know exactly how you fixed it ('I changed that and the error disappears' does not cut it - why exactly did the problem happen and why exactly did the change fix it), should you even think about adding multithreading to productive code. Otherwise, multithreading is just a huge pain.
Besides, if you have not really understood the problems above you cannot even decide if something could be multithreaded - a lot of people write 'multithreaded' code and by the time they have it largely bug-free it's so heavily synchronized it would have been better to keep it in one thread.
I have two problems with what you're doing. The first is that while the TimerManaged class has used synchronized to protected the shared vector of TimedEvent objects, the TimedEvent objects themselves are not safe. For example, look at the following method in the TimedEvent class:

[source]
public void reset()
{
lTimeTRef = mngr.getGlobalTimeTicks();
lTimeSRef = mngr.ticksToSeconds(lTimeTRef);
// System.out.println("TE[Reset] " + lTimeSRef);
}
[/source]

While you use the volatile keyword to make sure the long values lTimeTRef and lTimeSRef are not cached, that doesn't make the reset() method atomic. The context can switch after setting the lTimeTRef variable but before setting the lTimeSRef variable.

Also, in the TimerManaged, even though the monitor method is synchronized, nothing prevents other threads from modifying the TimedEvent objects.

[source]
if(te.monitor(te.getLocalTime(gTimeS))) // TimedEvent accessed
{
// something happened
tel.interpretTimedMessage("", te, te.getLocalTime(gTimeS)); // TimedEvent could have been changed by now
}
[/source]

Perhaps thinking about what is actually shared between threads would help.

I suspect that the reason adding the System.out.println makes the code work is that this changes the timing
enough to alter the runtime behavior, but that is just a guess.

The second thing is having a super tight while loop continuosly calling a snychronized method is the
wrong way to handle this. One of the import concepts to understand when writing threaded code like this
is that while single threaded code should not block, code like this needs to block whenever there isn't
anything to do.

When the TimerManaged first runs, and there are no events to process, it should block, and wait for
something to be added to the queue. Then, perhaps it sleeps for about as long as the expected message
time, then wakes up, sends the message, and goes back to sleep. As the others have already pointed out,
this is an extremely difficult thing to get right, and the chance for weird, random race conditions that are
almost impossible to trigger on purpose requires that you know what you're doing.

But the best way to learn this stuff is to do it, so drive on. Just know that it will not be easy. If it was me, this solution
seems way too hard, and I'd try and figure out another way to solve the problem without all the threads.

I think, therefore I am. I think? - "George Carlin"
My Website: Indie Game Programming

My Twitter: https://twitter.com/indieprogram

My Book: http://amzn.com/1305076532

Thanks glass, I tried fixing the issue but I took your suggesting and figured another way around the problem, I decided to have TimerManage extend TimerTask instead and have its run method be my monitor() call. This fixed the problem.

As for my first attempt with the thread please be advised that alot of what you see in the code came after desperation where it wasn't working where I thought it should so I added synchronized methods where it wouldn't of been practical just because I wanted to try to get my code to work and then work backwards from there.

I will try to find a way to implement this with threads in the future but for now extending TimerTask instead is doing a great job, so I recommend anyone that might want to do somethign similair give TimerTask a chance.

I will try to find a way to implement this with threads in the future but for now extending TimerTask instead is doing a great job, so I recommend anyone that might want to do somethign similair give TimerTask a chance.

When you're ready, I recommend

http://amzn.com/0321349601

I think, therefore I am. I think? - "George Carlin"
My Website: Indie Game Programming

My Twitter: https://twitter.com/indieprogram

My Book: http://amzn.com/1305076532

This topic is closed to new replies.

Advertisement