How to do Ordered Multithreading: 2 or more threads executing 1 same function.

Started by
24 comments, last by tom_mai78101 11 years, 6 months ago

There's a lot of discussion about splitting up the canvas into groups, and then let each thread draw the group. It gave me a whole lot of questions.

1. Wouldn't splitting up the Canvas into areas for threads to render on cause something that resembles tearing?

2. In Java, I do not know of a Java method that allows splitting bitmaps while it is being drawn. What is the logic for that? Test to see if the bitmap Rect area is over the threshold, and then stop drawing that area, while letting the other thread draw the undrawn area?

3. rip-off mentioned that I may be passing a gradually completing Canvas into methods. The possibility that this might be my bottleneck is actually quite high (as I haven't considered it before), as I have passed the same reference to the Canvas object through a lot of methods. Are there other alternatives to efficiently draw tons of bitmaps (of powers of 2, in my case) onto a Canvas?


1,2 you can access a bitmaps data array in Java (atleast on Android) and copy the parts of the array you want to the buffer you're blitting to.

3. yes, this is quite possible, unless you are blitting tons of bitmaps or using some really ineffective blitting method sending the canvas to the GPU will be the slow part. The Canvas in Java seems to be singlebuffered so you should blit to a software buffer (BufferedImage should work) and then blit that buffer to the canvas. (which means you only do a single blit to the canvas). Also, if you have layers or sections of layers that rarely change you can blit those to their own buffered images at launch + when they change and then blit those images to the main bufferedimage each frame.
[size="1"]I don't suffer from insanity, I'm enjoying every minute of it.
The voices in my head may not be real, but they have some good ideas!
Advertisement
"wouldn't that be a horizontal split, though?"

OK, I was terminologically unclear. Split it vertically into horizontal strips. Yes, the split line is horizontal.
The main problem for splitting the Canvas for threads to work on is the splitting part. I have not found so far a way to split a Canvas class object area into at least two areas. They said that the class Canvas doesn't "own" pixel data. The canvas I'm using to make drawing calls is always attached to the bitmap images.

I'm compelled to create more Canvas objects, and run threads through them. Merging Canvas is going to be a new topic for me, and so does trying to get 2 canvas to draw the same bitmap at different locations to match up with its universal coordinates (originally intended coordinates), because once they are split up, the X and Y destination coordinates for the bitmaps are not entirely the same as they should be.

For Android, in my opinion, their method of using their subset of Java would involve something like using Canvas constructor to create one from a bitmap. Maybe that counts as an BufferedImage substitute.

I did checked out a few resources about parallel programming for Java. The conncurrency keyword, "synchronized" is a total mystery for me. They also mentioned that how a "synchronized" keyword can also affect immensely the execution of threads. It would seemed I have reached a Pandora's Box, and probably may get my project totalled if I keep nudging towards it.
Can you tell us more about your game?

There are lots of techniques that can be used to speed up the game. Multi-threading is a very complex way - it generally requires large architectural changes to how your program works, and also can only provide at most a constant speedup N - where N is the number of distinct hardware "cores" that can be used simultaneously. The chances that your program will get such a boost are low, most real applications aren't perfectly parallisable.

There might be other ways which can offer a much larger speedup - algorithmic improvements, choosing the right data structures or improving your memory layout and access patterns.


I did checked out a few resources about parallel programming for Java. The conncurrency keyword, "synchronized" is a total mystery for me. They also mentioned that how a "synchronized" keyword can also affect immensely the execution of threads. It would seemed I have reached a Pandora's Box, and probably may get my project totalled if I keep nudging towards it.
[/quote]
Hold up - if you don't understand such things then you absolutely should not be using threads. Threads add some very complex behaviour to the otherwise simple to understand Java programming model. When two threads interact without correct communication, the results are undefined and can result in some bizarre, even seemingly "impossible" situations.

If you are serious about learning to write multi-threaded programs you need to build up this knowledge first.

Can you tell us more about your game?


My game is basically a player controlling a ball with an accelerometer embedded in a hardware system (such as an Android phone). The ball is used to knock other balls into a hole (the goal) in order to complete the level. There are many levels with objects interacting with the player's ball and the balls that should be knocked into the goal.

EDIT: I should mentioned that the accelerometer can easily change its X axis, Y axis, and Z axis values, simply because you are holding the accelerometer with your hands. It is not supposed to be placed on a table or on a hard surface.

About the game logic, the game itself is split into two groups, the Update group and the Rendering group. It's multithreaded and it's done in a simple way of abusing the nature of threads. Like many other people have said time and time again, threads are run inconsistently together, causing many race conditions and multiple complex problems if not handled correctly. By understanding the nature of how threads compete one another on the CPU cores (in this case, I have two cores), I utilize a parallel programming paradigm.

One thread is used to update logic ONLY. The other thread is used to render objects ONLY. And that's it. All variables are read-only if thread A wants to read what thread B is using, and vice versa. I have not done anything with concurrency in order to make both of these threads communicate with each other. I simply made them as separate processes with "global-ish" variables wrapped in a Java class (namespace, as you would call it).

When I test the program out, all is fine. Everything run as expected, and there's no bugs on the rendering or updates. It's a perfect combination. But, there are times when I felt it is running slow after a long period of processing the threads, updates, and rendering.



I did checked out a few resources about parallel programming for Java. The conncurrency keyword, "synchronized" is a total mystery for me. They also mentioned that how a "synchronized" keyword can also affect immensely the execution of threads. It would seemed I have reached a Pandora's Box, and probably may get my project totalled if I keep nudging towards it.

Hold up - if you don't understand such things then you absolutely should not be using threads. Threads add some very complex behaviour to the otherwise simple to understand Java programming model. When two threads interact without correct communication, the results are undefined and can result in some bizarre, even seemingly "impossible" situations.

If you are serious about learning to write multi-threaded programs you need to build up this knowledge first.
[/quote]

The only thing I understand about threads, is that they are unpredictable. So, I used this only understanding and wrote a medium-sized program that goes with this rule of thumb. Technically, it's a little bit like multithreading, but in reality, I really don't know a better way to describe this. "Separated threading" sounds a bit off, but that's a better description of it.

If "synchronized" is the answer to all of my questions, then it's either me not looking at it correctly or it's something I find it hard to grasp, yet can't find out why. But I did some more research before posting this.

The function pipe I drew in the diagram may well be a Java synchronized method. In order to let two threads go in one by one and on a tight, repeating loop, I have to understand the concept of notify(), notifyAll() and wait() and how it works in such a loop... This is the part where I'm probably stuck in, and had me lost my focus, I believe.

The only thing I understand about threads, is that they are unpredictable. So, I used this only understanding and wrote a medium-sized program that goes with this rule of thumb. Technically, it's a little bit like multithreading, but in reality, I really don't know a better way to describe this. "Separated threading" sounds a bit off, but that's a better description of it.
[/quote]

Sounds interesting. One thread/function that manages and executes different tasks, like when a multi-tasking OS is running 2 threads simultaneously on a single core (or running N+X threads on N cores, where X > 0)? I assume this requires that your tasks can suspend themselves after a timeslice (to give control back to the main function). Resembles coroutines (used within Lua to resemble multi-tasking) to me.


The only thing I understand about threads, is that they are unpredictable. So, I used this only understanding and wrote a medium-sized program that goes with this rule of thumb. Technically, it's a little bit like multithreading, but in reality, I really don't know a better way to describe this. "Separated threading" sounds a bit off, but that's a better description of it.


Sounds interesting. One thread/function that manages and executes different tasks, like when a multi-tasking OS is running 2 threads simultaneously on a single core (or running N+X threads on N cores, where X > 0)? I assume this requires that your tasks can suspend themselves after a timeslice (to give control back to the main function). Resembles coroutines (used within Lua to resemble multi-tasking) to me.
[/quote]

I think you could say that. I just don't have any better descriptive ways to say it though.

My game is basically a player controlling a ball with an accelerometer embedded in a hardware system (such as an Android phone). The ball is used to knock other balls into a hole (the goal) in order to complete the level. There are many levels with objects interacting with the player's ball and the balls that should be knocked into the goal.
[/quote]
This game doesn't sounds complicated enough to require multi-threading when rendering. Arguably it might not require any threading at all.


About the game logic, the game itself is split into two groups, the Update group and the Rendering group. It's multithreaded and it's done in a simple way of abusing the nature of threads. Like many other people have said time and time again, threads are run inconsistently together, causing many race conditions and multiple complex problems if not handled correctly. By understanding the nature of how threads compete one another on the CPU cores (in this case, I have two cores), I utilize a parallel programming paradigm.

One thread is used to update logic ONLY. The other thread is used to render objects ONLY. And that's it. All variables are read-only if thread A wants to read what thread B is using, and vice versa. I have not done anything with concurrency in order to make both of these threads communicate with each other. I simply made them as separate processes with "global-ish" variables wrapped in a Java class (namespace, as you would call it).

When I test the program out, all is fine. Everything run as expected, and there's no bugs on the rendering or updates. It's a perfect combination.
[/quote]
Unfortunately, it sounds like you have a latent bug. Your current combination of code, hardware and JVM implementation might not manifest the bug, but a change in any of these could break your program.

The problem is simple: Java doesn't guarantee that a thread will see a "consistent" view of the state of the program modified by other threads in the absence of synchronized (or Java classes/methods that have the same memory model semantics as synchronized (e.g. Atomic classes, volatile variables or explicit locks).

There are two particularly interesting cases of bugs caused by this.

One is that thread A does not see changes made by thread B at all - each thread might be only reading and writing to its own registers and processor caches, and in the absence of synchronization there is no reason for the JVM to explicitly let the processor know it must flush these local changes to main memory, and for the second thread to reload any changes.

The second is that thread A sees some of the actions of thread B, but in an unexpected order or even in seemingly "impossible" orders. For example, it is possible for a thread to see a partially constructed object in the following code:

private static Foo sharedFoo = null;

public static Foo getFoo()
{
if(sharedFoo == null) {
Foo foo = new Foo(42);
sharedFoo = foo;
}
return sharedFoo;
}

Even though Foo's constructor sets some field to the passed value, if two threads race on this method then it is possible for the following interaction to take place:

Thread A - Calls getFoo().getValue()
Thread A - Enter method getFoo()
Thread A - sharedFoo == null
Thread A - Allocate memory for "foo" // Note: for this example we presume the JVM zeros the allocated memory.
Thread A - set sharedFoo to reference this memory
>>> Context switch <<<
Thread B - Calls getFoo().getValue()
Thread B - Enter method getFoo()
Thread B - sharedFoo != null
Thread B - return sharedFoo
Thread B - getValue() returns 0 // !!!
>>> Context switch <<<
Thread A - Calls constructor for "foo"
Thread A - return sharedFoo
Thread A - getValue() returns 42

It might seem odd that the constructor of "foo" is run after the reference "sharedFoo" has been set, but this is a perfectly valid optimisation for the JVM/hardware to perform in the absence of synchronisation. If the constructor was even more complicated, you could end up with one thread seeing a class instance with the invariants broken!


But, there are times when I felt it is running slow after a long period of processing the threads, updates, and rendering.
[/quote]
If your game is running slow I am positive there are much better ways to get better performance. In any case, your first step should be to profile the application and determine the source of the slowness.

Adding threads can slow an application down - unless done with care.


If "synchronized" is the answer to all of my questions, then it's either me not looking at it correctly or it's something I find it hard to grasp, yet can't find out why. But I did some more research before posting this.

The function pipe I drew in the diagram may well be a Java synchronized method. In order to let two threads go in one by one and on a tight, repeating loop, I have to understand the concept of notify(), notifyAll() and wait() and how it works in such a loop... This is the part where I'm probably stuck in, and had me lost my focus, I believe.
[/quote]
One could use synchronized to get the kind of pattern you described above. However, the code would actually run slower than if you had a single thread alternating between the two actions.
Do you mean something like this? I happened to get this to work as I wanted to.

[source lang="java"]package test;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class Test {
Thread t1;
Thread t2;
Thread t3;
List<Integer> array = new ArrayList<Integer>();
Runnable run = new Runnable() {
@Override
public void run() {
while (array.size() < 50) {
synchronized (array) {
try {
array.wait();
}
catch (InterruptedException e) {
}
array.add(Thread.currentThread().hashCode());
System.out.println(Thread.currentThread().getName());
}
}
}
};
Runnable wait = new Runnable() {
@Override
public void run() {
Random r = new Random();
while (array.size() < 50) {
synchronized (array) {
try {
Thread.sleep(r.nextInt(200) + 100);
}
catch (InterruptedException e) {
e.printStackTrace();
}
array.notify();
}
}
}
};

public Test() {
t1 = new Thread(run);
t1.setName("Thread 1 - First Thread");
t2 = new Thread(run);
t2.setName("Thread 2 - Last Thread");
t3 = new Thread(wait);
}

public void start() {
t1.start();
t2.start();
t3.start();
}

public static void main(String[] ar) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
Test t = new Test();
t.start();
}
}
[/source]

What it does is it alternates between printing out "Thread 1 - First Thread" and "Thread 2- Last Thread" over and over again 50 times. I gave it a few extra milliseconds of time for it to completely finish the synchronized run() method, but that's only because I wanted to learn more about the keyword, [color=#800080]synchronized.

Anyway, if my current project is built on a latent bug in its concurrent state, I think the best method to increase performance would have to be what you recommended me to do, since you did say that if I were to modify the thread logic, I could break the build easily. If I just modify a small portion of the thread flow, it wouldn't hurt much at all.

My biggest proof to support that sentence is the 7 additional entities and 9 level maps I have added since I started the "separated threading" paradigm four months ago (July, at the time). I just kept adding and adding new game entities and maps, to the point that I'm just fixing small collision bugs here and there.

If I use only one single thread to work on both the Update group and the Rendering group, its work would be about twice the amount of work it takes for a thread to process either the Update group or the Rendering group, and not both. I did test to see if running on a single thread works or not, just by rewriting a small portion of my base code, where it is the place that initiated the separated threading. The result is actually slower than usual; the game runs sluggishly slow. I reverted it back, and it's now normal.

I really don't know how to say this, but I have to keep this "latent bug" existing on my build.

Gotta love JVM for this, I guess. Or in this case, gotta love Android's Dalvik VM.

EDIT: I hate my browser crashing...
The "wait" "run" code seems wrong to me:

  • Access to the "array" variable should be synchronized: (array.size() and array.add(...)).
  • That it alternates between both threads depends on the current JVM implementation. From Object.notify() javadoc: "If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation."
  • Even an "adecuate" JVM implementation is not enough to guarantee that.
  • The "run" code runs 50+1 times

This topic is closed to new replies.

Advertisement