ChorusOS 4.0 Introduction

Managing Per-Thread Data

One of the most common issues in a multithreaded environment is how to manage per-thread data structures. This may become an important question for libraries. In a single-threaded process, managing these data as global variables is fine. In a multithreaded environment, it will no longer work.

The ChorusOS operating system provides a convenient way for threads to manage per-thread data. A piece of data which needs to be instantiated on a per-thread basis must be associated with a unique key. The key may be obtained from the system through a call to ptdKeyCreate(). This data may accessed using specific calls named ptdSet() and ptdGet().

#include <pd/chPd.h>

int ptdKeyCreate(PdKey*  key,
                 KnPdHdl destructor);

ptdKeyCreate() generates a unique key, which is opaque to the user. This key is stored at the location defined by the key argument. The user may, optionally, specify a routine as the destructor argument. This routine will be invoked at thread deletion time and will be passed the value associated with key. Upon return from ptdKeyCreate(), the value associated with key is 0. This type of key is visible to all threads of the actor, but each thread using a given key will have its own private copy of the data.

#include <pd/chPd.h>

int ptdSet(PdKey  key,
           void*  value);

ptdSet() enables a thread to associate the value value with the key key which has been generated previously by a call to ptdKeyCreate().

#include <pd/chPd.h>

int ptdGet(PdKey  key);

ptdGet() returns the last value associated with the key by this same thread.

Example 6-6 includes a small library that returns a pointer to the next word of a string. This is a simplified version of the strtok() C library routine. For simplicity, it is assumed that words are always separated by spaces in the string.

This library is callable simultaneously from different threads, each thread working on its own string. The routine that returns the pointer to the next word does not take any parameters.

These routines are called snw routines (where snw stands for String Next Word). There is a snwSet(char *str) routine which defines the string that will be looked up by the invoking thread, and a char* snwGet() returning a pointer to the next word.

The library is invoked from the main thread and the created thread on two different strings in order to count the number of words in each string. The results are printed and the threads are synchronized before terminating the actor.

Refer to the ptdKeyCreate(2K), ptdSet(2K), and ptdGet(2K) man pages.


Example 6-6 Managing Per-Thread Data

(file: progov/perThreadData.c)

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <chorus.h>
#include <pd/chPd.h>

#define USER_STACK_SIZE (1024 * sizeof(long))			      
								      
KnSem   sampleSem;
PdKey   snwKey;

    int
childCreate(KnPc entry)
{
  KnActorPrivilege      actorP;
  KnDefaultStartInfo_f  startInfo;
  char*                 userStack;
  int                   childLid = -1;
  int                   res;

  startInfo.dsType            = K_DEFAULT_START_INFO;
  startInfo.dsSystemStackSize = K_DEFAULT_STACK_SIZE;

  res = actorPrivilege(K_MYACTOR, &actorP, NULL);
  if (res != K_OK) {
    printf("Cannot get the privilege of the actor, error %d\n", res);
    exit(1);
  }

  if (actorP == K_SUPACTOR) {
    startInfo.dsPrivilege = K_SUPTHREAD;
  } else {
    startInfo.dsPrivilege = K_USERTHREAD;
  }

  if (actorP != K_SUPACTOR) {
    userStack = malloc(USER_STACK_SIZE);
    if (userStack == NULL) {
      printf("Cannot allocate user stack\n");
      exit(1);
    }
    startInfo.dsUserStackPointer = userStack + USER_STACK_SIZE;
  } 

  startInfo.dsEntry = entry;

  res = threadCreate(K_MYACTOR, &childLid, K_ACTIVE, 0, &startInfo);
  if (res != K_OK) {
    printf("Cannot create the thread, error %d\n", res);
    exit(1);
  }

  return childLid;
}

    void
snwInit()
{
  int res;
      /* Just allocate a key for our "snw" library */
  res = ptdKeyCreate(&snwKey, NULL);
  if (res != K_OK) {						      
    printf("Cannot create a ptd key, error %d\n", res);		      
    exit(1);							      
  }								      
}

    void
snwSet(char* str)
{
   int res;

   res = ptdSet(snwKey, str);
   if (res != K_OK) {						      
     printf("Cannot set the ptd key, error %d\n", res);		      
     exit(1);							      
   }								      
}

    char*
snwGet()
{
  int    res;
  char*  p;
  char*  s;

  p = (char*)ptdGet(snwKey);
  if (p == NULL) return NULL;

  s = strchr(p, ' ');

  if (s != NULL) {
    s++;
  } else if (*p != '\0') {
      /* Last word might not have a following space */
    s = p + strlen(p);
  }

  res = ptdSet(snwKey, s);
  return s;
}

    void
sampleThread()
{
  char* ptr;
  int   words = 0;
  int   res;

  snwSet("This is the child thread!");

  for (ptr= snwGet(); ptr != NULL; ptr = snwGet()) {
    words++;
  }

  printf("Child thread found %d words.\n", words);

  res  = semV(&sampleSem);
  if (res != K_OK){						      
    printf("Cannot perform the semV operation, error %d\n", res);     
    exit(1);							      
  }								      
  threadDelete(K_MYACTOR, K_MYSELF);
}


int main(int argc, char** argv, char**envp)
{
  char*      ptr;
  int        words = 0;
  int        res;
  int        newThreadLi;

  res = semInit(&sampleSem, 0);
  if (res != K_OK) {						      
    printf("Cannot initialize the semaphore, error %d\n", res);	      
    exit(1);							      
  }								      

  snwInit();

  newThreadLi = childCreate((KnPc)sampleThread);

  snwSet("I am the main thread and counting words in this string!");

  for (ptr= snwGet(); ptr != NULL; ptr = snwGet()) {
    words++;
  }

  printf("Main thread found %d words.\n", words);

  res = semP(&sampleSem, K_NOTIMEOUT);
  if (res != K_OK) {						      
    printf("Cannot perform the semP operation, error %d\n", res);     
    exit(1);							      
  }								      

  return 0;
}