ChorusOS 5.0 Application Developer's Guide

Managing Per-Thread Data

One of the most common issues in a multithreaded environment is the management of per-thread data structures. This issue is particularly important in the context of libraries. In a single-threaded process, managing these data structures as global variables is sufficient. In a multithreaded environment, this approach no longer works.

The ChorusOS operating system provides a convenient way for threads to manage per-thread data. Any piece of data that needs to be instantiated on a per-thread basis must be associated with a unique key. This key can be obtained using the system call ptdKeyCreate(2K). This data may be accessed using ptdSet(2K) and ptdGet(2K).

#include <pd/chPd.h>
int ptdKeyCreate (PdKey* key, KnPdHdl destructor);

The ptdKeyCreate(2K) call generates a unique, opaque key. This key is stored in the location defined by the key argument. You may specify a routine as the destructor argument. This routine is invoked at thread deletion time and is passed the value associated with key. Upon return from ptdKeyCreate(2K), the value associated with key is 0. This type of key is visible to all threads of the actor, but each thread using a specific key has its own private copy of the data.

#include <pd/chPd.h>
int ptdSet (PdKey key, void* value);

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

#include <pd/chPd.h>
int ptdGet (PdKey key);

The ptdGet(2K) call returns the last value associated with the key by the same thread.

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

This library may be called 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). The snwSet(char *str) routine defines the string that will be looked up by the invoking thread, while the char* snwGet() routine returns 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.

For more information, refer to the ptdKeyCreate(2K), ptdSet(2K) and ptdGet(2K) man pages.


Example 7-3 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, NULL, &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);             
  }              
  (void) threadDelete(K_MYACTOR, K_MYSELF);
}
int main(int argc, char** argv, char**envp)
{
  char*      ptr;
  int        words = 0;
  int        res;
  res = semInit(&sampleSem, 0);
  if (res != K_OK) {            
    printf("Cannot initialize the semaphore, error %d\n", res);       
    exit(1);             
  }              
  snwInit();
  (void) 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;
}