>> 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

Reply via email to