Threads and Thread-Specific Data


This section covers os_thread , os_this_thread , os_future_thread , os_future , os_trap and discusses thread-specific data.

A thread is a program that is executing. Like processes, a thread can access most of its own attributes, but only a small subset of the attributes for another thread. Therefore, thread objects are modeled using two classes: os_this_thread , which represents the current thread, and os_thread , which represents other threads. The sections in this chapter describe the behaviors of all thread-related classes.

Thread-specific data can be used to create a unique key and then make an association between each thread and this key.

thread , this_thread

The class os_this_thread is comprised of static member functions that perform operations on the current thread, including facilities for sleeping and waiting for a thread. Instances of os_this_thread cannot be created.

To create a new thread, construct an os_thread with the address of a function, and a pointer to an optional argument. This creates a thread that executes the specified function with the supplied pointer as its argument. The function must be able to take a void* argument and return a void* .

Once constructed, an os_thread object contains functions for suspending, resuming, terminating, and changing the priority of the thread object. The new thread executes concurrently with other threads in the process and terminates when it reaches the end of the specified function.

The following example spawns two threads and controls them using a variety of intuitive functions.

Example <ospace/thread/examples/thread1.cpp>
#include <iostream>
#include <ospace/thread.h>

void*
f1( void* args )
  {
  for ( int i = 0; i < 7; i++ )
    {
    cout << "tid " << os_this_thread::tid() << ", loop " << i << endl;
    os_this_thread::sleep( 1 ); // Sleep for 1 second.
    }
  return 0; // Ignored in this example.
  }

void
main()
  {
  os_thread_toolkit intialize;

  cout << "Main thread has tid " << os_this_thread::tid() << endl;
  os_thread t1( f1 ); // Spawn new thread.
  cout << "thread t1 has tid " << t1.tid() << endl;
  os_thread t2( f1 ); // Spawn new thread.
  cout << "thread t2 has tid " << t2.tid() << endl;

  cout << "sleep for 3 seconds" << endl;
  os_this_thread::sleep( 3 );

  cout << "suspend thread t1" << endl;
  t1.suspend();

  os_this_thread::sleep( 3 );
  cout << "resume thread t1" << endl;
  t1.resume();

  cout << "wait for threads" << endl;
  os_this_thread::wait_for_thread( t1 );
  os_this_thread::wait_for_thread( t2 );

  cout << "main thread terminates" << endl;
  }

Main thread has tid 1
tid 4, loop 0
thread t1 has tid 4
tid 6, loop 0
thread t2 has tid 6
sleep for 3 seconds
tid 6, loop 1
tid 4, loop 1
tid 6, loop 2
tid 4, loop 2
tid 6, loop 3
suspend thread t1
tid 6, loop 4
tid 6, loop 5
tid 6, loop 6
resume thread t1
tid 4, loop 3
wait for threads
tid 4, loop 4
tid 4, loop 5
tid 4, loop 6
main thread terminates

Thread-Specific Data

Thread-specific data can be used to create a unique key with an association between each thread and this key.

To create a unique key of type os_thread_key_t , use allocate() . Then, to associate the key with a void pointer for a particular thread, use set() . Once the association is made, you can use get() to access the void pointer associated with the key.

The following example uses thread-specific data to associate a name with each thread. The main program creates a unique key for name associations, which is then associated with a particular thread by f1() . The f2() function uses the key to retrieve the name of the thread.

Example <ospace/thread/examples/thread2.cpp>
#include <iostream>
#include <string>
#include <ospace/thread.h>

os_thread_key_t string_key; // Handle for string description.

void
f2()
  {
  cout << "f2() is called by ";
  // Retrieve association.
  cout << * (string*) os_this_thread::get( string_key ) << endl;
  }

void*
f1( void* args )
  {
  os_this_thread::set( string_key, args ); // Add association.
  f2();
  return 0;
  }

void
main()
  {
  os_thread_toolkit initialize;
  string_key = os_this_thread::allocate(); // Allocate key.

  // Spawn two threads.
  os_thread t1( f1, (void*) new string( "thread 1" ) );
  os_thread t2( f1, (void*) new string( "thread 2" ) );

  cout << "wait for threads" << endl;
  os_this_thread::wait_for_thread( t1 );
  os_this_thread::wait_for_thread( t2 );
  }

f2() is called by thread 1
f2() is called by thread 2
wait for threads

future_thread

The class os_future_thread can be used to process information and store the value for later use when it is done processing. This permits the application to be independent of when the thread actually will finish processing and to retrieve the return value anytime after it is available. This feature makes asynchronous programming easier by encapsulating the responsibility to synchronize on the availability and validity of the result.

To create a new future thread, construct an os_future_thread with the address of a function, a pointer to an optional argument, and the address of a callback function. This creates a thread that executes the specified function with the supplied pointer as its argument and the specified callback function with the pointer to the return value as its argument. The functions must be able to take a void* argument and return a void* .

Once constructed, an os_future_thread object contains functions for starting, changing the user functions, and retrieving the value. The new thread executes concurrently with other threads in the process and can be restarted again.

The following example spawns a future thread and blocks at the method invocation result() until the value is available.

Example <ospace/thread/examples/future1.cpp>
#include <ospace/thread.h>
#include <iostream>

void*
do_action( void* )
  {
  os_this_thread::sleep( 1 );
  cout << "Exiting do_action" << endl;
  return (void*)123;
  }

int
main()
  {
  os_thread_toolkit init_thread;
  os_future_thread async_thread( (os_future_thread_func_t) do_action, 0, 0 );
  async_thread.start();
  os_future result = async_thread.result();
  long data = (long)result.redeem();
  cout << "Result of async thread = " << data << endl;
  return 0;
  }

Exiting do_action
Result of async thread = 123

future

An os_future holds the future result of a user supplied function. When the redeem() member is called, two possible actions occur. If the user function has finished and returned a value, the redeem function returns the value. If the user function is still executing, the redeem function will block until the return value can be obtained.

The following example spawns two future threads and four os_future objects, a pair for each os_future_thread result.

Example <ospace/thread/examples/future2.cpp>
#include <ospace/thread.h>
#include <iostream>

static int value = 2;
os_mutex value_mutex;

void*
do_action( void* )
  {
  int j;
  value_mutex.lock();
  j = value;
  value++;
  value_mutex.unlock();
  os_this_thread::sleep( j );
  cout << "Exiting do_action" << endl;
  return (void*)j;
  }

int
main()
  {
  os_thread_toolkit init_thread;
  os_future_thread async_thread1( (os_future_thread_func_t)do_action, 0 );
  os_future_thread async_thread2( (os_future_thread_func_t)do_action, 0 );

  async_thread1.start();
  async_thread2.start();

  os_future result1 = async_thread1.result();
  os_future result2 = async_thread1.result();
  os_future result3 = async_thread2.result();
  os_future result4 = async_thread2.result();

  long data = (long)result1.redeem();
  cout << "Thread 1, Future 1 value = " << data << endl;

  data = (long)result2.redeem();
  cout << "Thread 1, Future 2 value = " << data << endl;

  data = (long)result3.redeem();
  cout << "Thread 2, Future 3 value = " << data << endl;

  data = (long)result4.redeem();
  cout << "Thread 2, Future 4 value = " << data << endl;

  return 0;
  }

Exiting do_action
Thread 1, Future 1 value = 2
Thread 1, Future 2 value = 2
Exiting do_action
Thread 2, Future 3 value = 3
Thread 2, Future 4 value = 3

trap

The class os_trap can contain multiple os_future objects and returns the next future in the order of the time they were received. This reduces the wait time between future thread processing.

To add futures to the os_trap object, use the method add_to_trap() passing the os_future object as the argument, and conversely to remove futures, use remove_from_trap() passing the os_future object as the argument. Retrieve the next available future by using get_next().

The following example uses traps to get the next readily-available result.

Example <ospace/thread/examples/future3.cpp>
#include <ospace/thread.h>
#include <iostream>

static int value = 2;
os_mutex value_mutex;

void*
do_action( void* )
  {
  int j;
  value_mutex.lock();
  j = value;
  value++;
  value_mutex.unlock();
  os_this_thread::sleep( j );
  cout << "Exiting do_action" << endl;
  return (void*)j;
  }

int
main()
  {
  os_thread_toolkit init_thread;
  os_future_thread async_thread1( (os_future_thread_func_t) do_action, 0 );
  os_future_thread async_thread2( (os_future_thread_func_t) do_action, 0 );

  async_thread1.start();
  async_thread2.start();

  os_future result1 = async_thread1.result();
  os_future result2 = async_thread1.result();
  os_future result3 = async_thread2.result();
  os_future result4 = async_thread2.result();

  os_trap trap;

  trap.add_to_trap( result1 );
  trap.add_to_trap( result2 );
  trap.add_to_trap( result3 );
  trap.add_to_trap( result4 );

  for ( int i = 0; i < 4; i ++ )
    {
    os_future future_from_trap;
    future_from_trap = trap.get_next();
    cout << "From trap, data = " << (long)future_from_trap.redeem() << endl;
    }

  return 0;
  }

Exiting do_action
From trap, data = 2
From trap, data = 2
Exiting do_action
From trap, data = 3
From trap, data = 3

thread_pool

A thread pool is a collection of reusable threads that are used repeatedly to run small tasks. The benefits of reusable threads are the gains from using system resources for things other than thread creation, which has a noticeable impact on performance when the number of tasks and the frequency of task execution is significant.

An os_thread_pool is configurable by allowing adjustments of the maximum pool size, the maximum number of idle threads, and the keep alive time through its constructor at creation time or its methods. Information about the thread pool's state can be obtained through various methods. Tasks can be added to the thread pool for execution using the execute() method passing a pointer to the task as an argument. Each task is an object that implements a runnable interface, i.e. the void* run() method is called when it is executed.

Use of member template feature in the execute methods gives the flexibility of not restricting the thread pool instance to a specific task class.

In the following example, the print_jobs() function in the os_printer class uses a thread pool object to execute the print jobs. Initially, a thread pool is created with a maximum of 5 total threads and a maximim of 3 idle threads. The thread pool object is opened and the os_print_jobs runnable objects are executed for execution by idle threads. Later, more print jobs are executed and the thread pool is resized. The thread pool size, idle threads, and the pending tasks are repeated displayed to show how threads are created and destroyed when calls to resize are made.

Example <ospace/thread/examples/thrdpool1.cpp>
#include <iostream>
#include <vector>
#include <ospace/thread.h>

class os_print_job
  {
  public:
    os_print_job( int id = 0 ) : id_( id ) {}

    void* run()
      {
      cout << "\tPrinter executing job # " << id_ << endl;
      os_this_thread::sleep( id_ % 3 );
      return 0;
      }

  private:
    int id_;
  };


class os_printer
  {
  public:
    os_printer( int num_jobs );
    void print_jobs();

  private:
    static void display_stats( const os_thread_pool& tpool );
    os_vector< os_print_job > jobs_;
  };


os_printer::os_printer( int num_jobs )
  {
  for ( int i = 0; i < num_jobs; ++i )
    jobs_.push_back( os_print_job( i ) );
  }


/* static */ void
os_printer::display_stats( const os_thread_pool& tpool )
  {
  cout << "\n========Thread pool statistics========";
  cout << "\n  Max pool size = " << tpool.max_pool_size();
  cout << "\n  Current pool size = " << tpool.pool_size();
  cout << "\n  Max idle threads = " << tpool.max_idle_threads();
  cout << "\n  Idle threads = " << tpool.idle_threads();
  cout << "\n  Pending tasks = " << tpool.pending_tasks();
  cout << "\n======================================" << endl;
  }


void
os_printer::print_jobs()
  {
  os_thread_pool tpool( 5, 3 );

  cout << "\nCreating a thread pool of (max_pool = 5, max_idle = 3)" << endl;
  display_stats( tpool );

  cout << "\nScheduling tasks for thread pool execution" << endl;
  for ( int i= 0; i < jobs_.size(); ++i )
    tpool.execute( &jobs_[ i ] );

  os_this_thread::sleep( 2 );
  display_stats( tpool );

  cout << "\nResizing the thread pool to (max_pool =7, max_idle = 5)" << endl;
  tpool.max_idle_threads( 5 );
  tpool.max_pool_size( 7 );
  os_this_thread::sleep( 1 );
  display_stats( tpool );

  cout << "\nScheduling more tasks for thread pool execution" << endl;
  for ( int j= 0; j < jobs_.size(); ++j )
    tpool.execute( &jobs_[ j ] );

  cout << "\nResizing the thread pool to (max_pool =10, max_idle = 2)" << endl;
  tpool.max_idle_threads( 2 );
  tpool.max_pool_size( 10 );
  os_this_thread::sleep( 1 );
  display_stats( tpool );
  os_this_thread::sleep( 2 );
  display_stats( tpool );

  cout << "\nResizing the thread pool to (max_pool =5, max_idle = 1)" << endl;
  tpool.max_idle_threads( 1 );
  tpool.max_pool_size( 5 );
  os_this_thread::sleep( 2 );
  display_stats( tpool );

  cout << "\nWaiting for task execution completion and pool exit" << endl;
  tpool.wait_for_completion();
  display_stats( tpool );
  }


int
main()
  {
  os_thread_toolkit initialize;

  os_printer printer( 25 ); // Create 25 jobs.
  printer.print_jobs();

  return 0;
  }

Creating a thread pool of (max_pool = 5, max_idle = 3)

========Thread pool statistics========
  Max pool size = 5
  Current pool size = 0
  Max idle threads = 3
  Idle threads = 0
  Pending tasks = 0
======================================

Scheduling tasks for thread pool execution
	Printer executing job # 0
	Printer executing job # 1
	Printer executing job # 2
	Printer executing job # 3
	Printer executing job # 4
	Printer executing job # 5
	Printer executing job # 6
	Printer executing job # 7
	Printer executing job # 8
	Printer executing job # 9
	Printer executing job # 10
	Printer executing job # 11

========Thread pool statistics========
  Max pool size = 5
  Current pool size = 5
  Max idle threads = 3
  Idle threads = 0
  Pending tasks = 13
======================================

Resizing the thread pool to (max_pool =7, max_idle = 5)
	Printer executing job # 12
	Printer executing job # 13
	Printer executing job # 14
	Printer executing job # 15
	Printer executing job # 16
	Printer executing job # 17
	Printer executing job # 18
	Printer executing job # 19
	Printer executing job # 20
	Printer executing job # 21
	Printer executing job # 22

========Thread pool statistics========
  Max pool size = 7
  Current pool size = 7
  Max idle threads = 5
  Idle threads = 0
  Pending tasks = 2
======================================

Scheduling more tasks for thread pool execution

Resizing the thread pool to (max_pool =10, max_idle = 2)
	Printer executing job # 23
	Printer executing job # 24
	Printer executing job # 0
	Printer executing job # 1
	Printer executing job # 2
	Printer executing job # 3
	Printer executing job # 4
	Printer executing job # 5
	Printer executing job # 6
	Printer executing job # 7
	Printer executing job # 8
	Printer executing job # 9
	Printer executing job # 10
	Printer executing job # 11

========Thread pool statistics========
  Max pool size = 10
  Current pool size = 10
  Max idle threads = 2
  Idle threads = 0
  Pending tasks = 13
======================================
	Printer executing job # 12
	Printer executing job # 13
	Printer executing job # 14
	Printer executing job # 15
	Printer executing job # 16
	Printer executing job # 17
	Printer executing job # 18
	Printer executing job # 19
	Printer executing job # 20
	Printer executing job # 21
	Printer executing job # 22
	Printer executing job # 23
	Printer executing job # 24

========Thread pool statistics========
  Max pool size = 10
  Current pool size = 7
  Max idle threads = 2
  Idle threads = 2
  Pending tasks = 0
======================================

Resizing the thread pool to (max_pool =5, max_idle = 1)

========Thread pool statistics========
  Max pool size = 5
  Current pool size = 1
  Max idle threads = 1
  Idle threads = 1
  Pending tasks = 0
======================================

Waiting for task execution completion

========Thread pool statistics========
  Max pool size = 5
  Current pool size = 0
  Max idle threads = 1
  Idle threads = 0
  Pending tasks = 0
======================================

Copyright©1994-2026 Recursion Software LLC
All Rights Reserved - For use by licensed users only.