Hoppa till huvudinnehållet
Institutionen för informationsteknologi

Event objects in POSIX

Note: Everything on this page is presented as is. The code is public domain, university copyright rules do not apply.

Contrary to popular belief, event objects - a construct particular to MS Windows and not found in *NIX OS:es - are useful in many instances where exact thread execution control is needed.

However, if you post a question on a *NIX forum about how to implement event objects or something similar, you will likely get an answer along the lines of "just use mutexes". Unfortunately, there is no "just" about it. Implementing event objects is a bit tricky, as there are several instances where race conditions or deadlocks can occur.

There are a few web sites dedicated to the topic, but they are either extremely brief or overly complicated.

If you need to use event objects, chances are that you already have a win32 application that you want to port to a POSIX system. Porting should not require any extensive rewriting efforts. All you should need are a few lines like:

#ifdef UNIX
#include "event_objects_posix.c"
#endif

Details

There are a couple of constants missing that need to be defined:

#define DWORD            unsigned int
#define BOOL             int

#define INFINITE         -1     // usually set to -1
#define WAIT_OBJECT_0    0      // usually set to 0
#define WAIT_TIMEOUT     64     // arbitrary number
#define WAIT_FAILED      96     //       -"-
#define WAIT_ABANDONED_0 128    //       -"-

#define MIL 1000000L            // one million
#define BIL 1000000000L         // one billion
#ifndef ETIMEDOUT
#define ETIMEDOUT 110
#endif

ETIMEDOUT is a constant returned by some POSIX functions. It was however not defined on my system.

Every time a thread starts waiting for an event object, a list_element object is added to the event object:

typedef struct t_list_element
{
  pthread_mutex_t mutex;  // mutex for the conditional wait
  pthread_cond_t cond;
  struct t_list_element *prev, *next;
} *list_element;

If more than one thread is waiting for the same event object, the elements will be added in a linked list.

This is the event object:

typedef struct t_HANDLE
{
  list_element start, end;
  pthread_mutex_t mutex;
  BOOL flag;
} *HANDLE;

And these are the functions for adding and removing list elements:

void AddElement(HANDLE event_object, list_element le)
{
  if (event_object->start == NULL)
    event_object->start = event_object->end = le;
  else
    {
      event_object->end->next = le;
      le->prev = event_object->end;
      event_object->end = le;
    }
}

void RemoveElement(HANDLE event_object, list_element le)
{
  list_element ptr;

  if (event_object->start == event_object->end)
    event_object->start = event_object->end = NULL;
  else if (le == event_object->start)
    event_object->start = event_object->start->next;
  else if (le == event_object->end)
    event_object->end = event_object->end->prev;
  else
    for (ptr = event_object->start->next; ; ptr = ptr->next)
      if (ptr == le)
	{
	  ptr->prev->next = ptr->next;
	  ptr->next->prev = ptr->prev;
	  break;
	}
}

The CreateEvent function below is a bit rudimentary, and may have to stay that way. Auto-resetting introduces some race conditions which are not defined in the MSDN documentation -- not to my knowledge anyway -- and until I have the opportunity to test those situations resetting will only be manual.

// attribute not used, name not used
// manual_reset is ignored, always treated as true
HANDLE CreateEvent(void *attribute, BOOL manual_reset, BOOL initial_state, void *name)
{
  HANDLE event_object = malloc(sizeof (struct t_HANDLE));
  event_object->start = event_object->end = NULL;
  pthread_mutex_init(&event_object->mutex, NULL);
  event_object->flag = initial_state;
  return event_object;
}

BOOL SetEvent(HANDLE event_object)
{
  list_element ptr;

  pthread_mutex_lock(&event_object->mutex);
  event_object->flag = TRUE;
  for (ptr = event_object->start; ptr != NULL; ptr = ptr->next)
    {
      pthread_mutex_lock(&ptr->mutex);
      pthread_cond_signal(&ptr->cond);
      pthread_mutex_unlock(&ptr->mutex);
      if (ptr == event_object->end)
	break;
    }
  pthread_mutex_unlock(&event_object->mutex);
  return TRUE;
}

BOOL ResetEvent(HANDLE event_object)
{
  pthread_mutex_lock(&event_object->mutex);
  event_object->flag = FALSE;
  pthread_mutex_unlock(&event_object->mutex);
  return TRUE;
}

CloseHandle will not destroy an event object if there are threads waiting for it.

BOOL CloseHandle(HANDLE event_object)
{
  if (event_object->start != NULL)
    return FALSE;
  pthread_mutex_destroy(&event_object->mutex);
  free(event_object);
  return TRUE;
}

WaitForSingleObject is treated as a special case of WaitForMultipleObjects:

DWORD WaitForMultipleObjects(DWORD, HANDLE *, BOOL, int);
DWORD WaitForSingleObject(HANDLE event_object, int timeout_ms)
{
  return WaitForMultipleObjects(1, &event_object, FALSE, timeout_ms);
}

The current version of WaitForMultipleObjects will only wait for a single event to be set, not all of them. A more complete version may be implemented in the future, if time allows.

// wait_all is ignored, always treated as false
DWORD WaitForMultipleObjects(DWORD count, HANDLE *event_object, BOOL wait_all, int timeout_ms)
{
  struct t_list_element le;
  DWORD i, return_value;
  int check_value = -1;
  struct timespec t;
  struct timeval tv;

  if (count == 0)
    return WAIT_FAILED;

  for (i = 0; i < count; ++i)
    pthread_mutex_lock(&event_object[i]->mutex);

  pthread_mutex_init(&le.mutex, NULL);
  pthread_cond_init(&le.cond, NULL);

  for (i = 0; i < count; ++i)
    {
      AddElement(event_object[i], &le);
      if (event_object[i]->flag)
        check_value = 1;
    }

  if (check_value == -1)
    {
      pthread_mutex_lock(&le.mutex);

      if (timeout_ms != INFINITE)
        {
          gettimeofday(&tv);
          t.tv_nsec = tv.tv_usec * 1000  + ((long) timeout_ms) * MIL;
          t.tv_sec = tv.tv_sec;
          if (t.tv_nsec >= BIL)
            {
              t.tv_sec += t.tv_nsec / BIL;
              t.tv_nsec %= BIL;
            }
        }

      for (i = 0; i < count; ++i)
        pthread_mutex_unlock(&event_object[i]->mutex);

      if (timeout_ms == INFINITE)
        check_value = pthread_cond_wait(&le.cond, &le.mutex);
      else
        check_value = pthread_cond_timedwait(&le.cond, &le.mutex, &t);
      if (check_value == ETIMEDOUT)
        return_value = WAIT_TIMEOUT;
      else if (check_value != 0)
        return_value = WAIT_FAILED;

      pthread_mutex_unlock(&le.mutex);

      for (i = 0; i < count; ++i)
        pthread_mutex_lock(&event_object[i]->mutex);
    }

  for (i = 0; i < count; ++i)
    {
      RemoveElement(event_object[i], &le);
      if (event_object[i]->flag)
        return_value = i;
    }

  for (i = 0; i < count; ++i)
    pthread_mutex_unlock(&event_object[i]->mutex);

  pthread_mutex_destroy(&le.mutex);
  pthread_cond_destroy(&le.cond);

  return return_value;
}

In conclusion, the functions may be said to be version 0.5, since about half of the functionality is implemented.

Uppdaterad  2010-11-02 11:23:19 av Lars Melander.