zach has a really good point, but i want to clarify a little. his example might seem easier, but it’s only because he has a more advanced understanding of thread safety.
using a static array might look like this:
class ParticleSystem : ofThread {
protected:
static const int n = 1024;
Particle particles[n];
void threadedFunction() {while(true) {update();}}
public:
void draw() {
for(int i = 0; i < n; i++) {particles_.draw();}
}
void update() {
for(int i = 0; i < n; i++) {particles[i].update();}
for(int i = 0; i < n; i++) {particles[i].checkIfDead();}
}
}
technically, this breaks the rule from my previous post: multiple threads are accessing the same data, and at least one is modifying it. so long as you never break this rule, you will never get a crash. but as you acquire a deeper understanding of what’s happening (like zach), there are some ways to bend this rule. i think this is why threading seems so complicated. on the surface, it’s really just that one rule. but as you learn more you realize you can bend the rule in a few different ways.
here’s why you can bend this rule with a static array, but not a vector:
this code:
void draw() {
for(int i = 0; i < n; i++) {particles[i].draw();}
}
requires two things. it needs to know about the size of the array (n), and it needs to know the location in memory of each particle (particles). if either of these things are changed by update(), we can get a crash. fortunately, we’ve defined the size of the array to be constant, so that’s set. and when you have a static array of particles, they are given a fixed location in memory allocated at runtime.
with a vector, the size might change [i]and_ the memory locations might change. if we use a fixed size vector, then the size definitely won’t change. but stl does not guaranteed that the memory location will stay constant.*
that’s why you can use a static array, but not a vector.
unfortunately, that’s not all. if we take this route of avoiding lock(), things are still a little more complicated. let’s look at Particle and the draw() method:
class Particle {
protected:
bool dead;
ofVec2f position;
public:
void draw() {
if(!dead) {
ofCircle(position, 4);
}
}
...
}
draw() needs two things: dead and position. let’s say the only other place we’re using dead looks like this:
dead = true;
this is what’s called an “atomic” operation. that means that the CPU can’t be interrupted while doing the operation (“atomic” here means “indivisible”). an atomic operation is like surrounding a single operation with lock()/unlock().
but position might not be involved in an atomic operation. for example, maybe in update() we say:
position.x += velocity.x;
position.y += velocity.y;
(you should use the overloaded ofVec2f operations
but just for example). this means that the execution order of the code might look like:
...
position.x += velocity.x; // update thread
ofCircle(position, 4); // draw thread
position.y += velocity.y; // update thread
...
this will give you weird glitches when things are drawn. it won’t crash, but it won’t look right.
this case isn’t so bad, because the particle is probably moving a small amount. but if you change your code and do something a little more complex inside update(), you will have bigger glitches. for example, let’s say you’re doing something like an average of multiple velocities (weird example, i know):
for(int i = 0; i < velocities.size(); i++) {position += velocities[i];}
position /= velocities.size();
in this case your execution order might look like:
...
for(int i = 0; i < velocities.size(); i++) {position += velocities[i];} // update thread
ofCircle(position, 4); // draw thread
position /= velocities.size(); // update thread
and your particles will be drawn in crazy locations because the division hasn’t happened yet.
in short, zach’s solution can really speed things up. but it also requires a deeper understanding of thread safety, and can create bugs that are hard to track down. i recommend using double or triple buffering for now, because it’s easier and will behave more intuitively.
*in general, if you’re not adding/removing elements, then the vector memory location should stay constant, but it’s not guaranteed. stl calls itself “thread safe, but only if you’re not modifying the data”. to me (and zach) this doesn’t feel very “thread safe” at all
you can read more about stl here http://www.sgi.com/tech/stl/thread-safety.html