[PATCH] SDL 1.3: Drawing from one texture to another

Most threads use a loop of some sort to repeatedly perform some operation.
Usually, you want the operation itself to happen fairly quickly, but how much
time passes between each cycle is not particularly important.

Considering all that, how to set up your mutexes and state changes is fairly
obvious:

while(running) {
lockMutex();
setState(mystate);

performOperationUsingState();

unlockMutex();

}

Any code in another thread that you’re likely to care about then runs only
after unlockMutex and before the next lockMutex.

And again, you need that mutex even if you’re using passed state. The mutex
code could be placed in whatever library function you’re calling, but that
would be very bad, since locking a mutex is fairly expensive.

That’s a very, very bad idea.

If how much time passed between each iteration of the loop wasn’t important, you
wouldn’t be trying to do both cycles in parallel in the first place, now would you?
Let’s imagine we’ve got two loops going on at the same time, doing exactly what
you described. Since they’re both contending for the same mutex, one does one
iteration, then unlocks and waits for the other to do one iteration, then it switches
back to the first one, and so on.

Suddenly you’re not running in parallel anymore; you have a serialized operation,
and it’s even slower than it would have been in a single thread because of all the
mutex locks and unlocks (which, as you pointed out, are fairly expensive) and
context switches.From: llubnek@gmail.com (Kenneth Bull)
Subject: Re: [SDL] [PATCH] SDL 1.3: Drawing from one texture to another

Which is why many people tend to avoid multithreading.

The key here is that most of your threads should do more than one thing, so
that while one thread’s busy with one mutex, the rest are off doing their own
thing.

Or, even better, do almost everything involving a particular protected
resource using one thread, and then, very rarely, and very briefly, lock the
mutex from your main thread and check the results.On Monday, 6 July 2009 21:57:10 Mason Wheeler wrote:

That’s a very, very bad idea.

If how much time passed between each iteration of the loop wasn’t
important, you wouldn’t be trying to do both cycles in parallel in the
first place, now would you? Let’s imagine we’ve got two loops going on at
the same time, doing exactly what you described. Since they’re both
contending for the same mutex, one does one iteration, then unlocks and
waits for the other to do one iteration, then it switches back to the first
one, and so on.

Suddenly you’re not running in parallel anymore; you have a serialized
operation, and it’s even slower than it would have been in a single thread
because of all the mutex locks and unlocks (which, as you pointed out, are
fairly expensive) and context switches.

…you don’t “get” parallelism at all, do you? Yes, there are some advantages to
having completely different tasks running in different threads. Sound is a
perfect example. But the biggest benefit comes from doing the same thing in
multiple places at the same time. For example, Google manages to return a
nice, prompt search result from a data set that contains pretty much the entire
Internet by breaking the search down into thousands of smaller parts and having
a few thousand computers each process a bit of it at the same time. This is an
extreme example, of course, but it illustrates the true power of parallelism. And
that sort of computing is just not possible with shared global state.

The only time I ever use mutexes or critcal sections is to deliberately block a
thread for a non-trivial amount of time. For example, in my game engine, if two
threads are both running scripts and both scripts want to open a text box,
there’s only one text box to open, so it’s protected by a critical section. One
of them displays its message, and the other script has to wait its turn. But
dropping a heavyweight lock into a pair of loops is just asking for trouble.

That’s a very, very bad idea.

If how much time passed between each iteration of the loop wasn’t
important, you wouldn’t be trying to do both cycles in parallel in the
first place, now would you? Let’s imagine we’ve got two loops going on at
the same time, doing exactly what you described. Since they’re both
contending for the same mutex, one does one iteration, then unlocks and
waits for the other to do one iteration, then it switches back to the first
one, and so on.

Suddenly you’re not running in parallel anymore; you have a serialized
operation, and it’s even slower than it would have been in a single thread
because of all the mutex locks and unlocks (which, as you pointed out, are
fairly expensive) and context switches.

Which is why many people tend to avoid multithreading.

The key here is that most of your threads should do more than one thing, so
that while one thread’s busy with one mutex, the rest are off doing their own
thing.

Or, even better, do almost everything involving a particular protected
resource using one thread, and then, very rarely, and very briefly, lock the
mutex from your main thread and check the results.From: llubnek@gmail.com (Kenneth Bull)
Subject: Re: [SDL] [PATCH] SDL 1.3: Drawing from one texture to another
On Monday, 6 July 2009 21:57:10 Mason Wheeler wrote:

I “get” parallelism just fine…

While you can have multiple threads accessing different parts of the same data
set at the same time, you can’t have them accessing the same parts of the same
data set. You need to code your program to avoid such situations. One way to
do that, though often not the best way, is using mutexes.

As far as global / passed state is concerned, if the underlying system is
using a global state then you still need that mutex whether you like it or
not.

You could make a passed state API on top of it, but every time you call a
function in that API you need to lock a mutex, check your state handle, set
the state if necessary, perform your operation and unlock the mutex. If you
don’t use a mutex, then between checking the state and performing your
operation, some other thread may change the state and screw things up.

And yes, this is serial:

void Thread1(void* param) {
while (running) {
SDL_mutexP((SDL_mutex*)param);
fart_loudly();
SDL_mutexV((SDL_mutex*)param);
}
}

void Thread2(void* param) {
while(running){
SDL_mutexP((SDL_mutex*)param);
inhale();
SDL_mutexV((SDL_mutex*)param);
}
}

This is not:

void Thread1(void* param) {
while (running) {
SDL_mutexP((SDL_mutex*)param);
fart_loudly();
SDL_mutexV((SDL_mutex*)param);
belch();
}
}

void Thread2(void* param) {
while(running){
SDL_mutexP((SDL_mutex*)param);
inhale();
SDL_mutexV((SDL_mutex*)param);
exhale();
}
}

Nor is this:

void Thread1(void* param) {
while (running) {
SDL_mutexP(((SDL_mutex*)param)[0]);
fart_loudly();
SDL_mutexV(((SDL_mutex*)param)[0]);
SDL_mutexP(((SDL_mutex*)param)[1]);
belch();
SDL_mutexV(((SDL_mutex*)param)[1]);
}
}

void Thread2(void* param) {
while(running){
SDL_mutexP(((SDL_mutex*)param)[0]);
inhale();
SDL_mutexV(((SDL_mutex*)param)[0]);
SDL_mutexP(((SDL_mutex*)param)[1]);
exhale();
SDL_mutexV(((SDL_mutex*)param)[1]);
}
}

This is (more or less), but it’s useful anyway:

void Thread1(void* param) {
while (running) {
SDL_mutexP((SDL_mutex*)param);
fart_loudly();
SDL_mutexV((SDL_mutex*)param);
}
}

void Thread2(void* param) {
while(running){
SDL_mutexP((SDL_mutex*)param);
inhale();
SDL_mutexV((SDL_mutex*)param);
SDL_Delay(30);
}
}On Monday, 6 July 2009 22:39:07 Mason Wheeler wrote:

…you don’t “get” parallelism at all, do you? Yes, there are some
advantages to having completely different tasks running in different
threads. Sound is a perfect example. But the biggest benefit comes from
doing the same thing in multiple places at the same time. For example,
Google manages to return a nice, prompt search result from a data set that
contains pretty much the entire Internet by breaking the search down into
thousands of smaller parts and having a few thousand computers each process
a bit of it at the same time. This is an extreme example, of course, but
it illustrates the true power of parallelism. And that sort of computing
is just not possible with shared global state.

The only time I ever use mutexes or critcal sections is to deliberately
block a thread for a non-trivial amount of time. For example, in my game
engine, if two threads are both running scripts and both scripts want to
open a text box, there’s only one text box to open, so it’s protected by a
critical section. One of them displays its message, and the other script
has to wait its turn. But dropping a heavyweight lock into a pair of loops
is just asking for trouble.

On Mon, Jul 6, 2009 at 9:22 PM, Kenneth Bull wrote: >> Well, global state is
global, right, not per thread. And using a mutex >> kinds of negates the
point of threads (especially as you’d have to >> keep it for long periods of
time, or randomly insert “give up the >> mutex, re-take it, and re-select
the context” in your long >> operations). > > I think in some cases it ends
up thread local anyway. Things don’t “end up” thread local, you have to make
them thread local. > If these variables were referred to through pointers
instead, then it would > remain global, but they aren’t now. > You’d
probably end up holding a mutex for the video system in general. This I >
do not recommend, but fortunately the texture/renderer/multi-window system
is > new in 1.3 so backwards compatibility at least is not an issue. Using
a > passed state system there does not break any existing API from 1.2. With
X11, which is probably one of the most backward of the so-called "modern"
platforms, two threads can draw to their each of their own XShmImage (in SDL
1.3 parlance, a streamable texture) at the same time without any
coordination. Even with the fact that you have to avoid concurrent calls to
Xlib, sending an XCopyArea command through the X stream doesn’t take very
little time (compared to how long it will take to execute), so multiple
threads can send X commands in fairly rapid succession without interfering
with each others all that much (remember that each of these threads have to
do some work to produce those commands). > Personally, I like threads. If I
could I’d have a separate thread for > networking, another for video, a
third for sound, fourth for events, and > fifth, sixth, whatever, for
everything else. But I know better. I like threads when they work well.
They used not to work so well, but nowadays, things are better. But with my
threads, I like APIs that let me use them productively! > In general, I
prefer to assume the programmer is fairly intelligent and knows > what
he/she is doing. That said, I prefer API’s that don’t put many >
restrictions on how they can be used. I personally believe SDL should
support > multithreading, but let the user handle mutexes and such to avoid
the > performance penalty in single threaded applications. Where such
mutexes might > be necessary should, of course, be thoroughly documented
though… Oh, I agree. For example, I think that SDL should not provide
protection against two threads trying to write to a single texture. That’s a
bug, plain and simple, and if you’re going to use threads and share state
(in the form of the texture content), then you need to protect it with some
kind of lock, or otherwise you have a race condition. But since SDL has to
cover for some platform deficiencies in order to provide a consistent
interface, it might have some internal mutexes, of course. With X11, for
example, you can’t have two threads send commands at the same time, even if
they’re involving unrelated X resources. One of the things with newer
threading implementations is that locking non-contended mutexes (as they
will be in a single-threaded program) is extremely fast, about 50 cycles.
It’s too much to start spraying mutexes all over the place (appending a
character to a string with enough buffer space left, say, would be much
cheaper than taking a mutex, which would be unbearable), but if you’re
protecting sending data to another process or something like a blit, taking
the mutex is trivial in comparison. Still, they should be kept to a minimum,
as much as possible. – http://www.google.com/profiles/pphaneuf
[]