2.1.3. What Is The Interface

Typically, the creation and termination of processes and threads is directed by a pair of fork and join calls. The fork call forks a new process or thread off the active process or thread. The join call waits for a termination of a process or thread. The exact syntax and semantics depends on the particular operating system and programming language.

2.1.3.1. Example: Posix Process And Thread API

To create a process, the Posix standard defines the fork and execve calls. The fork call creates a child process, which copies much of the context of the parent process and begins executing just after the fork call with a return value of zero. The parent process continues executing after the fork call with the return value providing a unique identification of the child process. The child process typically continues by calling execve to execute a program different from that of the parent process.

To terminate a process, the Posix standard defines the exit and wait calls. The exit call terminates a process. The wait waits for a child process to terminate and returns its termination code. Additional ways for a process to terminate, both voluntarily or involuntarily, exist.

pid_t fork (void);
int execve (const char *filename, char *const argv [], char *const envp []);

pid_t wait (int *status);
pid_t waitpid (pid_t pid, int *status, int options);

void exit (int status);

The Posix standard call to create a thread is pthread_create, which takes the address of the function executed by the thread as its main argument. The pthread_join call waits for a thread to terminate, a thread can terminate for example by returning from the thread function or by calling pthread_exit. The pthread_detach call indicates that pthread_join will not be called on the given thread.

int pthread_create (
  pthread_t *thread,
  pthread_attr_t *attr,
  void * (*start_routine) (void *),
  void *arg);

int pthread_join (
  pthread_t thread,
  void **return_value);

void pthread_exit (
  void *return_value);

int pthread_detach (
  pthread_t thread);

The Posix standard also allows a thread to associate thread local data with a key and to retrieve thread local data of the current thread given the key.

int pthread_key_create (
  pthread_key_t *key,
  void (* destructor) (void *));

int pthread_setspecific (
  pthread_key_t key,
  const void *value);
void *pthread_getspecific (
  pthread_key_t key);

2.1.3.2. Example: Windows Process And Thread API

The Windows API provides the CreateProcess call to create a process, two of the main arguments of the call are the name of the program file to execute and the command line to supply to the process. The process can terminate by calling ExitProcess, the WaitForSingleObject call can be used to wait for the termination of a process.

Figure 2.10. Windows Process Creation System Calls

BOOL CreateProcess (
  LPCTSTR lpApplicationName,
  LPTSTR lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL bInheritHandles,
  DWORD dwCreationFlags,
  LPVOID lpEnvironment,
  LPCTSTR lpCurrentDirectory,
  LPSTARTUPINFO lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);

VOID ExitProcess (
  UINT uExitCode);

DWORD WaitForSingleObject (
  HANDLE hHandle,
  DWORD dwMilliseconds
);

Windows applications can create threads using the CreateThread call. Besides returning from the thread function, the thread can also terminate by calling ExitThread. The universal WaitForSingleObject call is used to wait for a thread to terminate.

Figure 2.11. Windows Thread Creation System Calls

HANDLE CreateThread (
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  SIZE_T dwStackSize,
  LPTHREAD_START_ROUTINE lpStartAddress,
  LPVOID lpParameter,
  DWORD dwCreationFlags,
  LPDWORD lpThreadId
);

VOID ExitThread (
  DWORD dwExitCode);

Windows also offers fibers as a lightweight variant to threads that is scheduled cooperatively rather than preemptively. Fibers are created using the CreateFiber call, scheduled using the SwitchToFiber call, and terminated using the DeleteFiber call. Before using fibers, the current thread has to initialize the fiber state information using the ConvertThreadToFiber call.

LPVOID ConvertThreadToFiber (
  LPVOID lpParameter);

LPVOID CreateFiber (
  SIZE_T dwStackSize,
  LPFIBER_START_ROUTINE lpStartAddress,
  LPVOID lpParameter);

VOID SwitchToFiber (
  LPVOID lpFiber);

VOID DeleteFiber (
  LPVOID lpFiber);

Windows also allows a thread or a fiber to associate thread local data or fiber local data with a key and to retrieve the data of the current thread or fiber given the key.

DWORD TlsAlloc (void);
BOOL TlsFree (
  DWORD dwTlsIndex);

BOOL TlsSetValue (
  DWORD dwTlsIndex,
  LPVOID lpTlsValue);
LPVOID TlsGetValue (
  DWORD dwTlsIndex);
DWORD FlsAlloc (
  PFLS_CALLBACK_FUNCTION lpCallback);
BOOL FlsFree (
  DWORD dwFlsIndex);

BOOL FlsSetValue (
  DWORD dwFlsIndex,
  PVOID lpFlsValue);
PVOID FlsGetValue (
  DWORD dwFlsIndex);

To permit graceful handling of stack overflow exceptions, it is also possible to set the amount of space available on the stack during the stack overflow exception handling.

BOOL SetThreadStackGuarantee (
  PULONG StackSizeInBytes);

2.1.3.3. Example: Linux Clone API

Linux offers an alternative process and thread creation API using the clone call.

int clone (
  int (*fn) (void *),
  void *child_stack,
  int flags,
  void *arg,
  ...);

2.1.3.4. Example: Posix Dynamic Linker API

The dynamic linker can be accessed through a standardized interface as well. The dlopen and dlclose calls are used to load and drop a dynamic library into and from the current process. Loading and dropping a library also involves calling its constructor and destructor functions. The dlsym call locates a symbol by name. Special handles can be used to look up symbols in the default symbol lookup order or in an order that facilitates symbol wrapping.

void *dlopen (
  const char *filename,
  int flag);
int dlclose (
  void *handle);
void *dlsym (
  void *handle,
  const char *symbol);

2.1.3.5. Example: Java Thread API

Java wraps the operating system threads with a Thread, whose run method can be redefined to implement the thread function. A thread begins executing when its start method is called, the stop method can be used to terminate the thread.

class java.lang.Thread implements java.lang.Runnable {
  java.lang.Thread ();
  java.lang.Thread (java.lang.Runnable);

  void start ();
  void run ();
  void interrupt ();
  boolean isInterrupted ();

  void join () throws java.lang.InterruptedException;
  void setDaemon (boolean);
  boolean isDaemon ();

  static java.lang.Thread currentThread ();
  static void yield ();
  static void sleep (long) throws java.lang.InterruptedException;

  ...
}

2.1.3.6. Example: OpenMP Thread API

The traditional imperative interface to creating and terminating threads can be too cumbersome especially when trying to create applications that use both uniprocessor and multiprocessor platforms efficiently. The OpenMP standard proposes extensions to C that allow to create and terminate threads declaratively rather than imperatively.

The basic tool for creating threads is the parallel directive, which states that the encapsulated block is to be executed by multiple threads. The for directive similarly states that the encapsulated cycle is to be iterated by multiple threads. The sections directive finally states that the encapsulated blocks are to be executed by individual threads. More directives are available for declaring thread local data and other features.

#pragma omp parallel private (iThreads, iMyThread)
{
  iThreads = omp_get_num_threads ();
  iMyThread = omp_get_thread_num ();
  ...
}

#pragma omp parallel for
  for (i = 0 ; i < MAX ; i ++)
    a [i] = 0;

#pragma omp parallel sections
{
  #pragma omp section
    DoOneThing ();
  #pragma omp section
    DoAnotherThing ();
}