// module threads2.c
// test frame for portable threads (PThreads) under Linux and Windows
// This version uses mutexes as signals to indicate the termination 
// of a thread! The main program (main thread) waits for the termination
// of all threads.
// Works under Windows 32 Bit and Linux
// EH 03-2009, 05-2016
// Windows 32 Bit: compile with gcc o threads.2exe threads2.c
// Linux: compile with gcc -o threads2 threads2.c -l pthread

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

// this is a very simple thread administration...not really used in this version
#define NUM_THREADS        10  // max. 10 threads
pthread_t thread_ids[NUM_THREADS];
pthread_mutex_t mutex1, mutex2;

#ifdef _WIN32
# include <windows.h>
#define sleep(x) Sleep(1000 * x)
#endif

// -------------------------------------------------
// thread worker routine: runs 10 secs
// Assumes that mutex1 is locked at start time
void *threadWorker1(void *pArg)
{
   int n, rc;
   long *pLong;
   //
   pLong = (long *)pArg;
   //
   printf("thread 1 arg=%ld\n", *pLong);
   for (n=0; n < 10; n++)
   {
     printf("Thread 1 n=%d\n", n);
     sleep(1);
     pthread_testcancel();   // this is necessary!!!
   }
   //
   // send a termination signal: the termination signal is the mutex itself!
   rc = pthread_mutex_unlock(&mutex1);
   if (rc != 0)
   {
     printf("thread 1: unlock ERROR\n");
   }
   else
     printf("thread 1: termination signal = mutex set\n");
   //
   zurueck:
   printf("leaving thread 1\n");
   pthread_exit(NULL);
}   // end threadWorker1()

// ----------------------------------------------------
// thread worker routine: runs 15 secs
void *threadWorker2(void *pArg)
{
   int n, rc;
   long *pLong;
   //
   pLong = (long *)pArg;
   printf("thread 2 arg=%ld\n", *pLong);
   for (n=0; n < 15; n++)
   {
     printf("Thread 2 n=%d\n", n);
     sleep(1);
     pthread_testcancel();   // this is necessary
   }
   //
   // send a termination signal: the termination signal is the mutex itself!
   rc = pthread_mutex_unlock(&mutex2);
   if (rc != 0)
   {
     printf("thread 2: unlock ERROR\n");
   }
   else
     printf("thread 2: termination signal = mutex set\n");
   //
   zurueck:
   printf("leaving thread 2\n");
   pthread_exit(NULL);
}   // end threadWorker2()

// ---------------------------------------------------------
// create and start a thread
// Returns 0 if OK, < 0 if error
int createThread(int threadNo,       // 0 .. (NUM_THREADS-1)
                 void *(*worker)(void *),  // address of worker routine
                 void *pArg)
{
  int ret = 0;
  printf("creating thread no. %d\n", threadNo);
  ret = pthread_create(&thread_ids[threadNo], 
                       NULL, 
                       worker,
                       pArg);  // argument passed to the worker routine
  if (ret != 0)
  {
    ret = -1;
    goto zurueck;
  }
  zurueck:
  printf("created  thread no. %d with result=%d\n", threadNo, ret);
  return ret;
}   // end createThread()

// ------------------------------------------------------
int main (int argc, char *argv[])
{
   int rc;
   int ret = 0;
   long arg1 = 101L;
   long arg2 = 102L;
   //
   rc = pthread_mutex_init(&mutex1, NULL);
   rc = pthread_mutex_init(&mutex2, NULL);
   //
   // create two threads and start them
   rc = createThread(0, threadWorker1, &arg1);
   if (rc != 0)
   {
     ret = -1;
     goto zurueck;
   }
   rc = pthread_mutex_lock(&mutex1);
   if (rc != 0)
   {
     ret = -2;
     goto zurueck;
   }
   printf("createThread() rc=%d\n", rc);
   printf("thread id=%08lx\n", (long)( thread_ids[0]));
   //
   rc = createThread(1, threadWorker2, &arg2);
   if (rc != 0)
   {
     ret = -3;
     goto zurueck;
   }
   rc = pthread_mutex_lock(&mutex2);
   if (rc != 0)
   {
     ret = -4;
     goto zurueck;
   }
   printf("createThread() rc=%d\n", rc);
   printf("thread id=%08lx\n", (long)( thread_ids[1]));
   //
   // Wait until all threads terminate.
   // The termination signal is the change of a mutex associated with each thread
   sleep(1);  // give the threads time to start
   int noThreads = 2;
   while (noThreads > 0)
   {
     // Note: this can be replaced and simplified by a loop over an array of mutexes
     // try to lock mutex1
     rc = pthread_mutex_trylock(&mutex1);
     if (rc != 0)  // errror handling is not complete (is simplified)
     {
       printf("main(): cannot lock - waiting\n");
     }
     else
     {
       printf("main(): lock 1 OK!!!\n");
       noThreads--;
       if (noThreads <= 0) break;
     }
     // try to lock mutex2
     rc = pthread_mutex_trylock(&mutex2);
     if (rc != 0)  // errror handling is not complete (is simplified)
     {
       printf("main(): cannot lock - waiting\n");
     }
     else
     {
       printf("main(): lock 2 OK!!!\n");
       noThreads--;
       if (noThreads <= 0) break;
     }
     sleep(1);
   }   // end while()
   //
   ret = 1;
   printf("main() all threads have terminated\n");
   //
   zurueck:
   printf("main() return code=%d\n", ret);
   return ret;
}   // end main()
 
