Re: mutex problems on Windows



On Wed, 2009-07-08 at 07:42 +0300, Dov Grobgeld wrote:
> That indeed is a more elegant solution! I really feel that I'm
> learning one step at a time... Regarding the use of g_atomic_int_...,
> I'm curious how you fit his into the flow? Obviously T and G must be
> initialized to share a stopping variable s. Who should create this
> variable? I would be interested to see how you would amend your
> 1-direction solution so that it also supports stopping.

A common pattern for simple background tasks that spit out a lot of
intermidiate data (with most of the tedious init, casting, cleanup,
error detection, and being compilable missing) could look something like
this:

int stop = 0;
GThread worker;
GAsyncQueue Q;

// when the user requests work
void on_work_button_pressed() {
    Q = g_asyc_queue_new();
    worker = g_thread_create( do_long_async_action, NULL, TRUE, NULL );
}

// running in the background thread
void do_long_async_action() {
    while( g_atomic_int_get(stop) ) {
        j = do_some_work();
        g_async_queue_put( Q, j );
        g_idle_add( some_work_complete );
        if( j->finished ) break;
    }
}

// running in the main thread out of the mainloop idle handler
void some_work_complete() {
    j = g_async_queue_get( Q );
    update_gui( j );
    free( j );
}

// when the user hits the cancel button
// or the action is finished
// or the program exits
void cleanup_worker() {
    g_atomic_int_set( stop, 1 );
    g_thread_join( worker );
}

It's pretty late at night here so use your discretion if you try to
adapt this directly.

A less pure approach (unless you count purely lazy), is to put a NULL or
other invalid value into the queue and have the worker stop on that.
Then you can get rid of the stop int, but have to deal with special
values instead.  This is really only a problem in glib, because it
doesn't like wrapping some ints in pointers.  In higher level languages
there is almost always a special None value that is not also 0, which
makes this approach much more reasonable elsewhere.

Of course, just as before where we can wrap a data-passing condition
variable with a queue, we can wrap this snippet in another layer of
abstraction with a GTask[1], OAsyncWorker[2], or one of the other
"async" tools out there.  The docs at [1] explain the thing nicely I
think.  That said, I haven't actually used this approach in any C
programs; they add a bit more weight, can be tricky to use efficiently,
aren't builtin to glib (yet), and the queue approach is not cumbersome
for small tasks.  In some cases though, they can be a really nice way to
make things clearer.  As with a lot of things, threading has no one
silver bullet, but there are a lot of different approaches out there and
they can each be really helpful against the right problem.

> Thanks!
> Dov

You're welcome,
-Terrence

[1] http://docs.dronelabs.com/gtask/
[2] http://live.gnome.org/AsyncWorker


> 2009/7/8 Terrence Cole <list-sink trainedmonkeystudios org>
>         
>         On Tue, 2009-07-07 at 10:56 +0300, Dov Grobgeld wrote:
>         > Thanks a lot for the help.! I have now solved the problem by
>         > introducing a GCond,called C below, into the flow. For
>         posterity, here
>         > is the modified flow, which is actually a general model of a
>         > client-server interaction between a worker thread and the
>         gui thread.
>         >
>         >    * W locks j->M through g_mutex_lock(j->M) so that G will
>         not send
>         > the condition signal until we are ready for it.
>         >    * W fills in j with various info to display.
>         >    * W calls g_idle_add(GSourceFunc(cb_update_job), j) to
>         indicate to
>         > G that there is info to display.
>         >    * W waits on C through g_cond_wait(j->C, j->M). This will
>         unblock
>         > j->M and allow G to continue.
>         >    * G is called in cb_update_job().
>         >    * G updates the gui, also possibly updates j based on GUI
>         interaction.
>         >    * G does g_mutex_lock(j->M) which causes it to wait until
>         W has
>         > reached g_cond_wait().
>         >    * G sends a condition signal through g_cond_signal(j->C)
>         >    * G does g_mutex_unlock(j->M). This will allow W to take
>         the lock
>         > in g_cond_wait().
>         >    * W wakes up and does a g_mutex_unlock(j->M) as it no
>         longer needs
>         > the lock on the mutex.
>         >    * W examines the return info that G filled in into j and
>         contiues
>         > or aborts its operation.
>         >
>         > Quite complex I have to admit. Is there a simpler way to
>         solve the
>         > same problem (query/response)?
>         
>         
>         Check out GAsyncQueue.  For 1-way traffic on a GAsyncQueue Q:
>            * W fills in j
>            * W does g_async_queue_push( Q, j )
>            * W does g_idle_add(GSourceFunc(cb_update_job),Q)
>            * G in cb_update_job does: j = g_async_queue_pop(Q);
>         
>         Internally, the GAsyncQueue is basically doing the logic you
>         have above,
>         but it's much easier to use and probably has more bug testing
>         and
>         performance polishing.
>         
>         The 2-way case is basically the same thing twice.  For
>         GAsyncQueues Q
>         and Q':
>            * W fills in j
>            * W does g_async_queue_push(Q,j)
>            * W does g_idle_add(GSourceFunc(cb_update_job),Q)
>            * G in cb_update_job does: j = g_async_queue_pop(Q);
>            * G modifies j
>            * G does a g_async_queue_push(Q',j)
>            * W does a j = g_async_queue_pop(Q')
>         
>         Note that this relies on Q' blocking W while it waits for a
>         response,
>         which will limit your throughput.  You will get much better
>         performance
>         if you organize your program to take advantage of the
>         pipelining nature
>         of the queue. That said, the trivial 2-queue case will not be
>         slower
>         than what you have already since it's doing the same thing.
>         
>         I usually end up with something simpler than full 2-way
>         communication
>         (or I go all the way to threaded modules).  For instance, if
>         the status
>         G is sending back is something trivial like "you should stop
>         now", then
>         using a g_atomic_int_* will be much cheaper, and simpler.
>         
>         Good Luck,
>         -Terrence
>         
>         
>         > Regards,
>         > Dov
>         >
>         > 2009/7/7 Chris Vine <chris cvine freeserve co uk>
>         > >
>         > > On Mon, 6 Jul 2009 17:13:07 +0300
>         > > Dov Grobgeld <dov grobgeld gmail com> wrote:
>         > >
>         > > > I'm having a problem with GMutex under Windows that
>         don't lock. The
>         > > > behaviour is definitely different from that under Linux.
>         > > >
>         > > > The system is composed of to threads. A gui thread G and
>         a worker
>         > > > thread W. The ping pong between the threads via a mutex
>         j->M should
>         > > > work as follows. j is a job data structure that carries
>         info between
>         > > > the worker thread and the gui thread.
>         > > >
>         > > >    1. W locks j->M through g_mutex_lock(j->M) so that a
>         subsequent
>         > > > lock will block.
>         > > >    2. W fills in j with various info to display.
>         > > >    3. W calls g_idle_add(GSourceFunc(cb_update_job), j)
>         to indicate
>         > > > to G that there is info to display.
>         > > >    4. W blocks on M through a second call to
>         g_mutex_lock(j->M)
>         > > >    5. G is called in cb_update_job() and updates the
>         gui, also
>         > > > possibly updates j, and then does g_mutex_unlock(j->M)
>         > > >    6. W wakes up and does a g_mutex_unlock(j->M) as it
>         no longer
>         > > > needs the lock on the mutex.
>         > > >    7. W examines the return info that G filled in into j
>         and contiues
>         > > > or aborts its operation.
>         > > >
>         > > > The problem on Win32 is that g_mutex_lock in 4 doesn't
>         block and the
>         > > > thread continues, which eventually will cause the system
>         to crash.
>         > > >
>         > > > Is something supposed to be different under Windows, or
>         should I file
>         > > > a bug?
>         > >
>         > > This won't work.  Mutexes have ownership once locked.  An
>         unlock
>         > > operation on a mutex must be carried out by the same
>         thread that locked
>         > > it.
>         > >
>         > > You could use condition variables to achieve what you
>         want.  It would
>         > > also be wise to read up a little more on threading, and in
>         particular
>         > > pthreads (which GThreads mimic).
>         > >
>         > > Chris
>         > >
>         
>         
>         > _______________________________________________
>         > gtk-list mailing list
>         > gtk-list gnome org
>         > http://mail.gnome.org/mailman/listinfo/gtk-list
>         
>         
> 
> 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]