>> Can you send a slightly bigger reproducer, which has the child thread
>> doing a few units of work that are passed to it from the parent thread?
Here it is (also attached q.c). Comments embedded.
#define _MULTI_THREADED
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// This test case tries to be a minimal reproducer of thread pool usage.
//
// We have a permanently working thread worker() which takes
// functions (callbacks) from somewhere (add_callback()) and executes them.
// This thread does not cary any state between callbacks.
// So, logically this is the same as if we destroyed the thread and
recreated it
// each time we have a new callback.
//
// Helgrind can hardly understand this by itself,
// but a simple source annotation should help.
// See ANNOTATE_BEGINNING_OF_CALLBACK/ANNOTATE_END_OF_CALLBACK
//
// This test program has 3 global variables, GLOB1, GLOB2, GLOB3
// GLOB1: no races reported
// GLOB2: helgrind reports a race but ANNOTATE_... should help.
// GLOB3: helgrind reports a race and ANNOTATE_... will not help. Can this
be fixed by other means?
int COND = 0; // condition for
cond_wait() loop
pthread_mutex_t MU = PTHREAD_MUTEX_INITIALIZER; // for CV
pthread_cond_t CV = PTHREAD_COND_INITIALIZER; // works with MU
typedef int (*F)(void);
pthread_mutex_t MU_CB = PTHREAD_MUTEX_INITIALIZER; // for callbacks
static F callback = NULL; // protected by MU
// not protected by locks, synchronized via cond_wait()
static int GLOB1 = 0, GLOB2 = 0, GLOB3 = 0;
// execute untill one of callbacks returns 1
void *worker(void *parm)
{
int stop = 0;
F f;
while(!stop){
pthread_mutex_lock(&MU_CB);
if(callback){
f = callback;
callback = NULL; // we are ready to take next callback
}else{
f = 0;
}
pthread_mutex_unlock(&MU_CB);
if(f){
// ANNOTATE_BEGINNING_OF_CALLBACK (helgrind's client request here)
stop = f();
// ANNOTATE_END_OF_CALLBACK (helgrind's client request here)
}
sleep(1); // don't burn CPU. Real thread pool will block instead of
sleeping.
}
}
void add_callback(F f)
{
pthread_mutex_lock(&MU_CB);
assert(callback == NULL);
callback = f;
pthread_mutex_unlock(&MU_CB);
}
int callback_do_something_usefull()
{
sleep(2); // we want waiter to get there first
GLOB1 = 2; // no race is reported
GLOB2 = 2; // helgrind reports a race
pthread_mutex_lock(&MU);
GLOB3 = 2;
pthread_mutex_unlock(&MU);
pthread_mutex_lock(&MU);
COND = 1;
pthread_cond_signal(&CV);
pthread_mutex_unlock(&MU);
return 0; // continue
}
int callback_finish_thread()
{
return 1; // stop
}
int main(int argc, char **argv)
{
pthread_t threadid;
// accessed before pthread_create()
GLOB1 = 1;
pthread_create(&threadid, NULL, worker, NULL);
// accessed after pthread_create() but before
ANNOTATE_BEGINNING_OF_CALLBACK
GLOB2 = 1;
add_callback(callback_do_something_usefull);
pthread_mutex_lock(&MU);
GLOB3 = 1;
pthread_mutex_unlock(&MU);
// now we wait untill callback_do_something_usefull() signals.
pthread_mutex_lock(&MU);
while (COND != 1) {
pthread_cond_wait(&CV, &MU);
}
pthread_mutex_unlock(&MU);
// at this point callback_do_something_usefull() has signalled
// and will never write to any of these variables again.
// I beleive that worker() can be removed from TSETs of
// all tree variables.
//
// It is still possible that callback_do_something_usefull()
// did not exit yet and we did not execute ANNOTATE_END_OF_CALLBACK.
//
//
// Currently, helgrind has the following info:
// GLOB1: Exclusive(main)
// GLOB2: ShMod(tset={main, worker}, lset={})
// GLOB3: ShMod(tset={main, worker}, lset={MU})
GLOB1 = 2; // helgrind does *not* report a race
GLOB2 = 2; // helgrind already reported a race in
callback_do_something_usefull()
GLOB3 = 2; // helgrind reports race here
// fprintf(stderr, "GLOB1: %d\n", GLOB1);
// fprintf(stderr, "GLOB2: %d\n", GLOB2);
// fprintf(stderr, "GLOB3: %d\n", GLOB3);
// real program will give more usefull callbacks here
add_callback(callback_finish_thread);
pthread_join(threadid, NULL);
return 0;
}
On Dec 5, 2007 2:24 PM, Julian Seward <[EMAIL PROTECTED]> wrote:
>
> > COND is accessed before cond_signal() in child thread
>
> yes
>
> > and after cond_wait() in parent thread.
>
> no:
>
> while (COND != 1) {
> fprintf(stderr, "Wait: COND: %d\n", COND);
> pthread_cond_wait(&CV, &MU);
> }
>
> If the parent only accessed COND after cond_wait, then Helgrind would
> not complain, as then it would understand that "exclusive ownership" is
> transferred from child to parent by the signal/wait pair. However, both
> parent and child access it before the signal/wait pair, COND is marked
> as being in shared ownership, and the above doesn't apply.
>
> > >> If you change the order of these two lines, to
> >
> > Sure. But thread_join is not what is usually done in presence of thread
> > pools...
> > Threads tend to live forever.
> > This program is just a short reproducer.
>
> Yes. I suspect what you are seeing is a problem to do with memory
> recycling. See Helgrind manual Sec 7.5 point 2.
>
> Can you send a slightly bigger reproducer, which has the child thread
> doing a few units of work that are passed to it from the parent thread?
>
> J
>
#define _MULTI_THREADED
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// This test case tries to be a minimal reproducer of thread pool usage.
//
// We have a permanently working thread worker() which takes
// functions (callbacks) from somewhere (add_callback()) and executes them.
// This thread does not cary any state between callbacks.
// So, logically this is the same as if we destroyed the thread and recreated it
// each time we have a new callback.
//
// Helgrind can hardly understand this by itself,
// but a simple source annotation should help.
// See ANNOTATE_BEGINNING_OF_CALLBACK/ANNOTATE_END_OF_CALLBACK
//
// This test program has 3 global variables, GLOB1, GLOB2, GLOB3
// GLOB1: no races reported
// GLOB2: helgrind reports a race but ANNOTATE_... should help.
// GLOB3: helgrind reports a race and ANNOTATE_... will not help. Can this be fixed by other means?
int COND = 0; // condition for cond_wait() loop
pthread_mutex_t MU = PTHREAD_MUTEX_INITIALIZER; // for CV
pthread_cond_t CV = PTHREAD_COND_INITIALIZER; // works with MU
typedef int (*F)(void);
pthread_mutex_t MU_CB = PTHREAD_MUTEX_INITIALIZER; // for callbacks
static F callback = NULL; // protected by MU
// not protected by locks, synchronized via cond_wait()
static int GLOB1 = 0, GLOB2 = 0, GLOB3 = 0;
// execute untill one of callbacks returns 1
void *worker(void *parm)
{
int stop = 0;
F f;
while(!stop){
pthread_mutex_lock(&MU_CB);
if(callback){
f = callback;
callback = NULL; // we are ready to take next callback
}else{
f = 0;
}
pthread_mutex_unlock(&MU_CB);
if(f){
// ANNOTATE_BEGINNING_OF_CALLBACK (helgrind's client request here)
stop = f();
// ANNOTATE_END_OF_CALLBACK (helgrind's client request here)
}
sleep(1); // don't burn CPU. Real thread pool will block instead of sleeping.
}
}
void add_callback(F f)
{
pthread_mutex_lock(&MU_CB);
assert(callback == NULL);
callback = f;
pthread_mutex_unlock(&MU_CB);
}
int callback_do_something_usefull()
{
sleep(2); // we want waiter to get there first
GLOB1 = 2; // no race is reported
GLOB2 = 2; // helgrind reports a race
pthread_mutex_lock(&MU);
GLOB3 = 2;
pthread_mutex_unlock(&MU);
pthread_mutex_lock(&MU);
COND = 1;
pthread_cond_signal(&CV);
pthread_mutex_unlock(&MU);
return 0; // continue
}
int callback_finish_thread()
{
return 1; // stop
}
int main(int argc, char **argv)
{
pthread_t threadid;
// accessed before pthread_create()
GLOB1 = 1;
pthread_create(&threadid, NULL, worker, NULL);
// accessed after pthread_create() but before ANNOTATE_BEGINNING_OF_CALLBACK
GLOB2 = 1;
add_callback(callback_do_something_usefull);
pthread_mutex_lock(&MU);
GLOB3 = 1;
pthread_mutex_unlock(&MU);
// now we wait untill callback_do_something_usefull() signals.
pthread_mutex_lock(&MU);
while (COND != 1) {
pthread_cond_wait(&CV, &MU);
}
pthread_mutex_unlock(&MU);
// at this point callback_do_something_usefull() has signalled
// and will never write to any of these variables again.
// I beleive that worker() can be removed from TSETs of
// all tree variables.
//
// It is still possible that callback_do_something_usefull()
// did not exit yet and we did not execute ANNOTATE_END_OF_CALLBACK.
//
//
// Currently, helgrind has the following info:
// GLOB1: Exclusive(main)
// GLOB2: ShMod(tset={main, worker}, lset={})
// GLOB3: ShMod(tset={main, worker}, lset={MU})
GLOB1 = 2; // helgrind does *not* report a race
GLOB2 = 2; // helgrind already reported a race in callback_do_something_usefull()
GLOB3 = 2; // helgrind reports race here
// fprintf(stderr, "GLOB1: %d\n", GLOB1);
// fprintf(stderr, "GLOB2: %d\n", GLOB2);
// fprintf(stderr, "GLOB3: %d\n", GLOB3);
// real program will give more usefull callbacks here
add_callback(callback_finish_thread);
pthread_join(threadid, NULL);
return 0;
}
-------------------------------------------------------------------------
SF.Net email is sponsored by: The Future of Linux Business White Paper
from Novell. From the desktop to the data center, Linux is going
mainstream. Let it simplify your IT future.
http://altfarm.mediaplex.com/ad/ck/8857-50307-18918-4
_______________________________________________
Valgrind-developers mailing list
Valgrind-developers@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/valgrind-developers