ChorusOS 5.0 Application Developer's Guide

Multithreaded Programming

Within an actor, whether user or supervisor, more than one thread may execute concurrently. For an overview of the ChorusOS multithreaded model, refer to the "Microkernel" chapter in the ChorusOS 5.0 Features and Architecture Overview.

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;
}

Threads and Libraries

As illustrated in the previous example, it is often the case that C and C++ libraries have been designed for UNIX processes which were initially mono-threaded entities. To enable C programmers to continue using the usual libraries within multithreaded actors, the ChorusOS operating environment provides a set of adapted C libraries. These can be used from different threads of a given actor without encountering problems.

Some of these adapted libraries (printf(), fprintf(), fopen(), and malloc()) were used in the previous examples. All of these C libraries have been adapted to work efficiently, even within a multithreaded actor. Modifications are not visible to the programmer. They rely mainly on synchronization such as mutexes for protecting critical sections, and on the per-thread data mechanism to store per-thread global data.

Some libraries required no modification and work in a straightforward fashion within a multithreaded actor. These libraries, such as strtol() (string to lower case), work exclusively on local variables and do not access or generate any global states.