C++程序  |  265行  |  7.19 KB

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
/* This is really a test of semaphore handling
   (sem_{init,destroy,post,wait}).  Using semaphores a barrier
   function is created.  Helgrind-3.3 (p.k.a Thrcheck) does understand
   the barrier semantics implied by the barrier, as pieced together
   from happens-before relationships obtained from the component
   semaphores.  However, it does falsely report one race.  Ah well.
   Helgrind-3.4 is pure h-b and so reports no races (yay!). */
/* This code is derived from
   gcc-4.3-20071012/libgomp/config/posix/bar.c, which is

   Copyright (C) 2005 Free Software Foundation, Inc.
   Contributed by Richard Henderson <rth@redhat.com>.

   and available under version 2.1 or later of the GNU Lesser General
   Public License.

   Relative to the libgomp sources, the gomp_barrier_t type here has
   an extra semaphore field, xxx.  This is not functionally useful,
   but it is used to create enough extra inter-thread dependencies
   that the barrier-like behaviour of gomp_barrier_t is evident to
   Thrcheck.  There is no other purpose for the .xxx field. */
static sem_t* my_sem_init(char*, int, unsigned);
static int my_sem_destroy(sem_t*);
static int my_sem_wait(sem_t*); static int my_sem_post(sem_t*);
typedef struct
{
  pthread_mutex_t mutex1;
  pthread_mutex_t mutex2;
  sem_t* sem1;
  sem_t* sem2;
  unsigned total;
  unsigned arrived;
  sem_t* xxx;
} gomp_barrier_t;

typedef long bool;

void
gomp_barrier_init (gomp_barrier_t *bar, unsigned count)
{
  pthread_mutex_init (&bar->mutex1, NULL);
  pthread_mutex_init (&bar->mutex2, NULL);
  bar->sem1 = my_sem_init ("sem1", 0, 0);
  bar->sem2 = my_sem_init ("sem2", 0, 0);
  bar->xxx  = my_sem_init ("xxx",  0, 0);
  bar->total = count;
  bar->arrived = 0;
}

void
gomp_barrier_destroy (gomp_barrier_t *bar)
{
  /* Before destroying, make sure all threads have left the barrier.  */
  pthread_mutex_lock (&bar->mutex1);
  pthread_mutex_unlock (&bar->mutex1);

  pthread_mutex_destroy (&bar->mutex1);
  pthread_mutex_destroy (&bar->mutex2);
  my_sem_destroy(bar->sem1);
  my_sem_destroy(bar->sem2);
  my_sem_destroy(bar->xxx);
}

void
gomp_barrier_reinit (gomp_barrier_t *bar, unsigned count)
{
  pthread_mutex_lock (&bar->mutex1);
  bar->total = count;
  pthread_mutex_unlock (&bar->mutex1);
}

void
gomp_barrier_wait (gomp_barrier_t *bar)
{
  unsigned int n;
  pthread_mutex_lock (&bar->mutex1);

  ++bar->arrived;

  if (bar->arrived == bar->total)
    {
      bar->arrived--;
      n = bar->arrived;
      if (n > 0) 
        {
          { unsigned int i;
            for (i = 0; i < n; i++)
              my_sem_wait(bar->xxx); // acquire an obvious dependency from
              // all other threads arriving at the barrier
          }
          // 1 up n times, 2 down once
          // now let all the other threads past the barrier, giving them
          // an obvious dependency with this thread.
          do
            my_sem_post (bar->sem1); // 1 up
          while (--n != 0);
          // and wait till the last thread has left
          my_sem_wait (bar->sem2); // 2 down
        }
      pthread_mutex_unlock (&bar->mutex1);
      /* "Resultats professionnels!"  First we made this thread have an
         obvious (Thrcheck-visible) dependency on all other threads
         calling gomp_barrier_wait.  Then, we released them all again,
         so they all have a (visible) dependency on this thread.
         Transitively, the result is that all threads leaving the
         barrier have a a Thrcheck-visible dependency on all threads
         arriving at the barrier.  As required. */
    }
  else
    {
      pthread_mutex_unlock (&bar->mutex1);
      my_sem_post(bar->xxx);
      // first N-1 threads wind up waiting here
      my_sem_wait (bar->sem1); // 1 down 

      pthread_mutex_lock (&bar->mutex2);
      n = --bar->arrived; /* XXX see below */
      pthread_mutex_unlock (&bar->mutex2);

      if (n == 0)
        my_sem_post (bar->sem2); // 2 up
    }
}


/* re XXX, thrcheck reports a race at this point.  It doesn't
   understand that bar->arrived is protected by mutex1 whilst threads
   are arriving at the barrier and by mutex2 whilst they are leaving,
   but not consistently by either of them.  Oh well. */

static gomp_barrier_t bar;

/* What's with the volatile here?  It stops gcc compiling
   "if (myid == 4) { unprotected = 99; }" and
   "if (myid == 3) { unprotected = 88; }" into a conditional
   load followed by a store.  The cmov/store sequence reads and
   writes memory in all threads and cause Thrcheck to (correctly)
   report a race, the underlying cause of which is that gcc is
   generating non threadsafe code.  

   (The lack of) thread safe code generation by gcc is currently a
   hot topic.  See the following discussions:
     http://gcc.gnu.org/ml/gcc/2007-10/msg00266.html
     http://lkml.org/lkml/2007/10/24/673
   and this is interesting background:
     www.hpl.hp.com/techreports/2004/HPL-2004-209.pdf
*/
static volatile long unprotected = 0;

void* child ( void* argV )
{
   long myid = (long)argV;
   //   assert(myid >= 2 && myid <= 5);

   /* First, we all wait to get to this point. */
   gomp_barrier_wait( &bar );

   /* Now, thread #4 writes to 'unprotected' and so becomes its
      owner. */
   if (myid == 4) {
      unprotected = 99;
   }

   /* Now we all wait again. */
   gomp_barrier_wait( &bar );

   /* This time, thread #3 writes to 'unprotected'.  If all goes well,
      Thrcheck sees the dependency through the barrier back to thread
      #4 before it, and so thread #3 becomes the exclusive owner of
      'unprotected'. */
   if (myid == 3) {
      unprotected = 88;
   }

   /* And just to be on the safe side ... */
   gomp_barrier_wait( &bar );
   return NULL;
}


int main (int argc, char *argv[])
{
   long i; int res;
   pthread_t thr[4];
   fprintf(stderr, "starting\n");

   gomp_barrier_init( &bar, 4 );

   for (i = 0; i < 4; i++) {
      res = pthread_create( &thr[i], NULL, child, (void*)(i+2) );
      assert(!res);
   }

   for (i = 0; i < 4; i++) {
      res = pthread_join( thr[i], NULL );
      assert(!res);
   }

   gomp_barrier_destroy( &bar );

   /* And finally here, the root thread can get exclusive ownership
      back from thread #4, because #4 has exited by this point and so
      we have a dependency edge back to the write it did. */
   fprintf(stderr, "done, result is %ld, should be 88\n", unprotected);

   return 0;
}







static sem_t* my_sem_init (char* identity, int pshared, unsigned count)
{
   sem_t* s;

#if defined(VGO_linux) || defined(VGO_solaris)
   s = malloc(sizeof(*s));
   if (s) {
      if (sem_init(s, pshared, count) < 0) {
	 perror("sem_init");
	 free(s);
	 s = NULL;
      }
   }
#elif defined(VGO_darwin)
   char name[100];
   sprintf(name, "anonsem_%s_pid%d", identity, (int)getpid());
   name[ sizeof(name)-1 ] = 0;
   if (0) printf("name = %s\n", name);
   s = sem_open(name, O_CREAT | O_EXCL, 0600, count);
   if (s == SEM_FAILED) {
      perror("sem_open");
      s = NULL;
   }
#else
#  error "Unsupported OS"
#endif

   return s;
}

static int my_sem_destroy ( sem_t* s )
{
   return sem_destroy(s);
}

static int my_sem_wait(sem_t* s)
{
  return sem_wait(s);
}

static int my_sem_post(sem_t* s)
{
  return sem_post(s);
}